diff --git a/configure b/configure index 98a55b5..ddbdb07 100755 --- a/configure +++ b/configure @@ -710,6 +710,8 @@ with_libxml with_libxslt with_system_tzdata with_zlib +enable_selinux +SELINUX_LIBS GREP EGREP ELF_SYS @@ -1378,6 +1380,7 @@ Optional Features: --enable-thread-safety make client libraries thread-safe --enable-thread-safety-force force thread-safety despite thread test failure + --enable-selinux enable to build with SELinux support --disable-float4-byval disable float4 passed by value --disable-float8-byval disable float8 passed by value --disable-largefile omit support for large files @@ -5532,6 +5535,183 @@ fi # +# SELinux support +# + +pgac_args="$pgac_args enable_selinux" + +# Check whether --enable-selinux was given. +if test "${enable_selinux+set}" = set; then + enableval=$enable_selinux; + case $enableval in + yes) + : + ;; + no) + : + ;; + *) + { { echo "$as_me:$LINENO: error: no argument expected for --enable-selinux option" >&5 +echo "$as_me: error: no argument expected for --enable-selinux option" >&2;} + { (exit 1); exit 1; }; } + ;; + esac + +else + enable_selinux=no + +fi + + +if test "$enable_selinux" = yes; then + SELINUX_LIBS="-lselinux" + { echo "$as_me:$LINENO: checking for avc_netlink_loop in -lselinux" >&5 +echo $ECHO_N "checking for avc_netlink_loop in -lselinux... $ECHO_C" >&6; } +if test "${ac_cv_lib_selinux_avc_netlink_loop+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&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 avc_netlink_loop (); +int +main () +{ +return avc_netlink_loop (); + ; + 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 "echo \"\$as_me:$LINENO: $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 + 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 && + $as_test_x conftest$ac_exeext; then + ac_cv_lib_selinux_avc_netlink_loop=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_selinux_avc_netlink_loop=no +fi + +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 +{ echo "$as_me:$LINENO: result: $ac_cv_lib_selinux_avc_netlink_loop" >&5 +echo "${ECHO_T}$ac_cv_lib_selinux_avc_netlink_loop" >&6; } +if test $ac_cv_lib_selinux_avc_netlink_loop = yes; then + +cat >>confdefs.h <<_ACEOF +#define HAVE_SELINUX 1 +_ACEOF + +else + { { echo "$as_me:$LINENO: error: \"--enable-selinux requires libselinux.\"" >&5 +echo "$as_me: error: \"--enable-selinux requires libselinux.\"" >&2;} + { (exit 1); exit 1; }; } +fi + + { echo "$as_me:$LINENO: checking for audit_open in -laudit" >&5 +echo $ECHO_N "checking for audit_open in -laudit... $ECHO_C" >&6; } +if test "${ac_cv_lib_audit_audit_open+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-laudit $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 audit_open (); +int +main () +{ +return audit_open (); + ; + 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 "echo \"\$as_me:$LINENO: $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 + 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 && + $as_test_x conftest$ac_exeext; then + ac_cv_lib_audit_audit_open=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_audit_audit_open=no +fi + +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 +{ echo "$as_me:$LINENO: result: $ac_cv_lib_audit_audit_open" >&5 +echo "${ECHO_T}$ac_cv_lib_audit_audit_open" >&6; } +if test $ac_cv_lib_audit_audit_open = yes; then + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBAUDIT 1 +_ACEOF + + SELINUX_LIBS="$SELINUX_LIBS -laudit" +fi + + + +fi + +# # Elf # @@ -28137,11 +28317,11 @@ with_libxml!$with_libxml$ac_delim with_libxslt!$with_libxslt$ac_delim with_system_tzdata!$with_system_tzdata$ac_delim with_zlib!$with_zlib$ac_delim +enable_selinux!$enable_selinux$ac_delim +SELINUX_LIBS!$SELINUX_LIBS$ac_delim GREP!$GREP$ac_delim EGREP!$EGREP$ac_delim ELF_SYS!$ELF_SYS$ac_delim -LDFLAGS_SL!$LDFLAGS_SL$ac_delim -LD!$LD$ac_delim _ACEOF if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 97; then @@ -28183,6 +28363,8 @@ _ACEOF ac_delim='%!_!# ' for ac_last_try in false false false false false :; do cat >conf$$subs.sed <<_ACEOF +LDFLAGS_SL!$LDFLAGS_SL$ac_delim +LD!$LD$ac_delim with_gnu_ld!$with_gnu_ld$ac_delim ld_R_works!$ld_R_works$ac_delim RANLIB!$RANLIB$ac_delim @@ -28245,7 +28427,7 @@ vpath_build!$vpath_build$ac_delim LTLIBOBJS!$LTLIBOBJS$ac_delim _ACEOF - if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 60; then + if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 62; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 diff --git a/configure.in b/configure.in index 1a68732..16f6eab 100644 --- a/configure.in +++ b/configure.in @@ -764,6 +764,24 @@ PGAC_ARG_BOOL(with, zlib, yes, AC_SUBST(with_zlib) # +# SELinux support +# +PGAC_ARG_BOOL(enable, selinux, no, + [enable to build with SELinux support]) +if test "$enable_selinux" = yes; then + SELINUX_LIBS="-lselinux" + AC_CHECK_LIB(selinux, avc_netlink_loop, + AC_DEFINE_UNQUOTED(HAVE_SELINUX, 1, + [SE-PostgreSQL feature is enabled]), + AC_MSG_ERROR("--enable-selinux requires libselinux.")) + AC_CHECK_LIB(audit, audit_open, + AC_DEFINE_UNQUOTED(HAVE_LIBAUDIT, 1) + SELINUX_LIBS="$SELINUX_LIBS -laudit") + AC_SUBST(enable_selinux) + AC_SUBST(SELINUX_LIBS) +fi + +# # Elf # diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 6244e8a..39e2493 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -165,6 +165,7 @@ enable_nls = @enable_nls@ enable_debug = @enable_debug@ enable_dtrace = @enable_dtrace@ enable_coverage = @enable_coverage@ +enable_selinux = @enable_selinux@ enable_thread_safety = @enable_thread_safety@ python_includespec = @python_includespec@ @@ -184,6 +185,8 @@ TCL_INCLUDE_SPEC = @TCL_INCLUDE_SPEC@ TCL_SHARED_BUILD = @TCL_SHARED_BUILD@ TCL_SHLIB_LD_LIBS = @TCL_SHLIB_LD_LIBS@ +SELINUX_LIBS = @SELINUX_LIBS@ + PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ PTHREAD_LIBS = @PTHREAD_LIBS@ diff --git a/src/backend/Makefile b/src/backend/Makefile index baa45e1..bc13bb9 100644 --- a/src/backend/Makefile +++ b/src/backend/Makefile @@ -16,7 +16,7 @@ include $(top_builddir)/src/Makefile.global SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \ main nodes optimizer port postmaster regex rewrite \ - storage tcop tsearch utils $(top_builddir)/src/timezone + security storage tcop tsearch utils $(top_builddir)/src/timezone include $(srcdir)/common.mk @@ -40,6 +40,9 @@ LIBS := $(filter-out -lpgport, $(LIBS)) $(LDAP_LIBS_BE) # The backend doesn't need everything that's in LIBS, however LIBS := $(filter-out -lz -lreadline -ledit -ltermcap -lncurses -lcurses, $(LIBS)) +# SELinux Libraries +LIBS += $(SELINUX_LIBS) + ########################################################################## all: submake-libpgport postgres $(POSTGRES_IMP) diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index a86716e..7c4d1f6 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -60,6 +60,7 @@ #include "access/heapam.h" #include "access/sysattr.h" #include "access/tuptoaster.h" +#include "catalog/pg_security.h" #include "executor/tuptable.h" @@ -287,6 +288,7 @@ heap_attisnull(HeapTuple tup, int attnum) case MinCommandIdAttributeNumber: case MaxTransactionIdAttributeNumber: case MaxCommandIdAttributeNumber: + case SecurityAttributeNumber: /* these are never null */ break; @@ -599,6 +601,9 @@ heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull) case TableOidAttributeNumber: result = ObjectIdGetDatum(tup->t_tableOid); break; + case SecurityAttributeNumber: + result = securitySysattSecLabelOut(tup->t_tableOid, tup); + break; default: elog(ERROR, "invalid attnum: %d", attnum); result = 0; /* keep compiler quiet */ @@ -722,6 +727,8 @@ heap_form_tuple(TupleDesc tupleDescriptor, if (tupleDescriptor->tdhasoid) len += sizeof(Oid); + if (tupleDescriptor->tdhassecid) + len += sizeof(Oid); hoff = len = MAXALIGN(len); /* align user data safely */ @@ -753,6 +760,8 @@ heap_form_tuple(TupleDesc tupleDescriptor, if (tupleDescriptor->tdhasoid) /* else leave infomask = 0 */ td->t_infomask = HEAP_HASOID; + if (tupleDescriptor->tdhassecid) + td->t_infomask |= HEAP_HASSECID; heap_fill_tuple(tupleDescriptor, values, @@ -864,6 +873,8 @@ heap_modify_tuple(HeapTuple tuple, newTuple->t_tableOid = tuple->t_tableOid; if (tupleDesc->tdhasoid) HeapTupleSetOid(newTuple, HeapTupleGetOid(tuple)); + if (HeapTupleHasSecid(newTuple)) + HeapTupleSetSecid(newTuple, HeapTupleGetSecid(tuple)); return newTuple; } @@ -1474,6 +1485,8 @@ heap_form_minimal_tuple(TupleDesc tupleDescriptor, if (tupleDescriptor->tdhasoid) len += sizeof(Oid); + if (tupleDescriptor->tdhassecid) + len += sizeof(Oid); hoff = len = MAXALIGN(len); /* align user data safely */ @@ -1495,6 +1508,8 @@ heap_form_minimal_tuple(TupleDesc tupleDescriptor, if (tupleDescriptor->tdhasoid) /* else leave infomask = 0 */ tuple->t_infomask = HEAP_HASOID; + if (tupleDescriptor->tdhassecid) + tuple->t_infomask |= HEAP_HASSECID; heap_fill_tuple(tupleDescriptor, values, diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 8582a7d..7df1631 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -88,6 +88,7 @@ CreateTemplateTupleDesc(int natts, bool hasoid) desc->tdtypeid = RECORDOID; desc->tdtypmod = -1; desc->tdhasoid = hasoid; + desc->tdhassecid = false; desc->tdrefcount = -1; /* assume not reference-counted */ return desc; @@ -121,6 +122,7 @@ CreateTupleDesc(int natts, bool hasoid, Form_pg_attribute *attrs) desc->tdtypeid = RECORDOID; desc->tdtypmod = -1; desc->tdhasoid = hasoid; + desc->tdhassecid = false; desc->tdrefcount = -1; /* assume not reference-counted */ return desc; @@ -150,6 +152,7 @@ CreateTupleDescCopy(TupleDesc tupdesc) desc->tdtypeid = tupdesc->tdtypeid; desc->tdtypmod = tupdesc->tdtypmod; + desc->tdhassecid = tupdesc->tdhassecid; return desc; } @@ -208,6 +211,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc) desc->tdtypeid = tupdesc->tdtypeid; desc->tdtypmod = tupdesc->tdtypmod; + desc->tdhassecid = tupdesc->tdhassecid; return desc; } @@ -314,6 +318,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (tupdesc1->tdhasoid != tupdesc2->tdhasoid) return false; + if (tupdesc1->tdhassecid != tupdesc2->tdhassecid) + return false; for (i = 0; i < tupdesc1->natts; i++) { diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index b0a911e..697946b 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -54,6 +54,7 @@ #include "catalog/namespace.h" #include "miscadmin.h" #include "pgstat.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/freespace.h" #include "storage/lmgr.h" @@ -2016,6 +2017,12 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, Oid simple_heap_insert(Relation relation, HeapTuple tup) { + /* + * SELinux assigns default security label for the tuple, + * but does not check permissions to the internal operations. + */ + sepgsqlHeapTupleInsert(relation, tup, true); + return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL); } @@ -2558,6 +2565,11 @@ l2: Assert(!(newtup->t_data->t_infomask & HEAP_HASOID)); } + /* Preserve SecurityId, if not changed */ + if (HeapTupleHasSecid(newtup) && + !OidIsValid(HeapTupleGetSecid(newtup))) + HeapTupleSetSecid(newtup, HeapTupleGetSecid(&oldtup)); + newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK); newtup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK); newtup->t_data->t_infomask |= (HEAP_XMAX_INVALID | HEAP_UPDATED); @@ -3499,6 +3511,8 @@ heap_inplace_update(Relation relation, HeapTuple tuple) memcpy((char *) htup + htup->t_hoff, (char *) tuple->t_data + tuple->t_data->t_hoff, newlen); + if (HeapTupleHeaderGetSecid(htup) != HeapTupleGetSecid(tuple)) + HeapTupleHeaderSetSecid(htup, HeapTupleGetSecid(tuple)); MarkBufferDirty(buffer); diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index 7b2ebe5..6179e52 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -591,6 +591,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, hoff += BITMAPLEN(numAttrs); if (newtup->t_data->t_infomask & HEAP_HASOID) hoff += sizeof(Oid); + if (HeapTupleHasSecid(newtup)) + hoff += sizeof(Oid); hoff = MAXALIGN(hoff); Assert(hoff == newtup->t_data->t_hoff); /* now convert to a limit on the tuple data size */ @@ -864,6 +866,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, new_len += BITMAPLEN(numAttrs); if (olddata->t_infomask & HEAP_HASOID) new_len += sizeof(Oid); + if (HeapTupleHeaderHasSecid(olddata)) + new_len += sizeof(Oid); new_len = MAXALIGN(new_len); Assert(new_len == olddata->t_hoff); new_data_len = heap_compute_data_size(tupleDesc, @@ -1015,6 +1019,8 @@ toast_flatten_tuple_attribute(Datum value, new_len += BITMAPLEN(numAttrs); if (olddata->t_infomask & HEAP_HASOID) new_len += sizeof(Oid); + if (HeapTupleHeaderHasSecid(olddata)) + new_len += sizeof(Oid); new_len = MAXALIGN(new_len); Assert(new_len == olddata->t_hoff); new_data_len = heap_compute_data_size(tupleDesc, @@ -1213,6 +1219,12 @@ toast_save_datum(Relation rel, Datum value, int options) memcpy(VARDATA(&chunk_data), data_p, chunk_size); toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull); + /* + * NOTE: SE-PostgreSQL does not assign any security label + * for tuples within the TOASTVALUE relation, so we omit + * to put sepgsqlHeapTupleInsert() hook here. + */ + heap_insert(toastrel, toasttup, mycid, options, NULL); /* diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index abf1ca1..e6db40a 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -36,6 +36,8 @@ #include "libpq/be-fsstubs.h" #include "miscadmin.h" #include "pgstat.h" +#include "security/rowlevel.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/fd.h" #include "storage/lmgr.h" @@ -140,6 +142,8 @@ typedef struct TransactionStateData Oid prevUser; /* previous CurrentUserId setting */ int prevSecContext; /* previous SecurityRestrictionContext */ bool prevXactReadOnly; /* entry-time xact r/o state */ + char *prevSecLabel; /* previous security label of client */ + int prevRowlv; /* previous Row-level control behavior */ struct TransactionStateData *parent; /* back link to parent */ } TransactionStateData; @@ -168,6 +172,8 @@ static TransactionStateData TopTransactionStateData = { InvalidOid, /* previous CurrentUserId setting */ 0, /* previous SecurityRestrictionContext */ false, /* entry-time xact r/o state */ + NULL, /* previous security label of client */ + ROWLV_FILTER_MODE, /* previous Row-level control behavior */ NULL /* link to parent state block */ }; @@ -1527,6 +1533,9 @@ StartTransaction(void) /* SecurityRestrictionContext should never be set outside a transaction */ Assert(s->prevSecContext == 0); + s->prevSecLabel = sepgsqlGetClientLabel(); + s->prevRowlv = rowlvGetPerformingMode(); + /* * initialize other subsystems for new transaction */ @@ -2031,6 +2040,12 @@ AbortTransaction(void) SetUserIdAndSecContext(s->prevUser, s->prevSecContext); /* + * Reset SELinux features + */ + sepgsqlSetClientLabel(s->prevSecLabel); + rowlvSetPerformingMode(s->prevRowlv); + + /* * do abort processing */ AfterTriggerEndXact(false); @@ -3874,6 +3889,12 @@ AbortSubTransaction(void) SetUserIdAndSecContext(s->prevUser, s->prevSecContext); /* + * Reset SELinux features + */ + sepgsqlSetClientLabel(s->prevSecLabel); + rowlvSetPerformingMode(s->prevRowlv); + + /* * We can skip all this stuff if the subxact failed before creating a * ResourceOwner... */ @@ -4015,6 +4036,8 @@ PushTransaction(void) s->blockState = TBLOCK_SUBBEGIN; GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext); s->prevXactReadOnly = XactReadOnly; + s->prevSecLabel = sepgsqlGetClientLabel(); + s->prevRowlv = rowlvGetPerformingMode(); CurrentTransactionState = s; diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 8d3f6c2..8700483 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -42,6 +42,7 @@ #include "nodes/pg_list.h" #include "nodes/primnodes.h" #include "rewrite/prs2lock.h" +#include "security/sepgsql.h" #include "storage/block.h" #include "storage/fd.h" #include "storage/ipc.h" @@ -211,6 +212,13 @@ Boot_CreateStmt: else { Oid id; + Oid *secLabels = + sepgsql_relation_create(LexIDStr($5), + RELKIND_RELATION, + tupdesc, + PG_CATALOG_NAMESPACE, + NULL, NIL, + false, false); id = heap_create_with_catalog(LexIDStr($5), PG_CATALOG_NAMESPACE, @@ -225,7 +233,8 @@ Boot_CreateStmt: 0, ONCOMMIT_NOOP, (Datum) 0, - true); + true, + secLabels); elog(DEBUG4, "relation created with oid %u", id); } do_end(); diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 13d5bcb..0de0c85 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -26,12 +26,14 @@ #include "access/xact.h" #include "bootstrap/bootstrap.h" #include "catalog/index.h" +#include "catalog/pg_security.h" #include "catalog/pg_type.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "postmaster/bgwriter.h" #include "postmaster/walwriter.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/ipc.h" #include "storage/proc.h" @@ -338,6 +340,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) case WalWriterProcess: statmsg = "wal writer process"; break; +#ifdef HAVE_SELINUX + case SelinuxReceiverProcess: + statmsg = "selinux netlink receiver"; + break; +#endif default: statmsg = "??? process"; break; @@ -430,6 +437,12 @@ AuxiliaryProcessMain(int argc, char *argv[]) WalWriterMain(); proc_exit(1); /* should never return */ +#ifdef HAVE_SELINUX + case SelinuxReceiverProcess: + sepgsqlReceiverMain(); + proc_exit(1); /* should nener return */ +#endif + default: elog(PANIC, "unrecognized process type: %d", auxType); proc_exit(1); @@ -497,6 +510,11 @@ BootstrapModeMain(void) */ boot_yyparse(); + /* + * SELinux initial labeling + */ + sepgsqlPostBootstrapingMode(); + /* Perform a checkpoint to ensure everything's down to disk */ SetProcessingMode(NormalProcessing); CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE); @@ -794,6 +812,8 @@ InsertOneTuple(Oid objectid) tupDesc = CreateTupleDesc(numattr, RelationGetForm(boot_reldesc)->relhasoids, attrtypes); + tupDesc->tdhassecid = RelationGetDescr(boot_reldesc)->tdhassecid; + tuple = heap_form_tuple(tupDesc, values, Nulls); if (objectid != (Oid) 0) HeapTupleSetOid(tuple, objectid); diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 3644ca6..632e3ed 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -13,7 +13,7 @@ include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ pg_aggregate.o pg_constraint.o pg_conversion.o pg_depend.o pg_enum.o \ pg_inherits.o pg_largeobject.o pg_namespace.o pg_operator.o pg_proc.o \ - pg_shdepend.o pg_type.o storage.o toasting.o + pg_security.o pg_shdepend.o pg_type.o storage.o toasting.o BKIFILES = postgres.bki postgres.description postgres.shdescription @@ -34,7 +34,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ pg_database.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ - pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ + pg_security.h pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ toasting.h indexing.h \ diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 1be417c..6139e4b 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -37,6 +37,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_proc.h" +#include "catalog/pg_security.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_type.h" #include "catalog/pg_ts_config.h" @@ -45,6 +46,7 @@ #include "foreign/foreign.h" #include "miscadmin.h" #include "parser/parse_func.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" @@ -699,6 +701,12 @@ expand_all_col_privileges(Oid table_oid, Form_pg_class classForm, if (curr_att == ObjectIdAttributeNumber && !classForm->relhasoids) continue; + /* Skip OID column, if it doesn't exist */ + if (curr_att == SecurityAttributeNumber && + (classForm->relkind != RELKIND_RELATION || + table_oid == SecurityRelationId)) + continue; + /* Views don't have any system columns at all */ if (classForm->relkind == RELKIND_VIEW && curr_att < 0) continue; @@ -801,6 +809,8 @@ ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname, relOid, grantorId, ACL_KIND_COLUMN, relname, attnum, NameStr(pg_attribute_tuple->attname)); + /* SELinux checks */ + sepgsql_attribute_grant(relOid, attnum); /* * Generate new ACL. @@ -1056,6 +1066,8 @@ ExecGrant_Relation(InternalGrant *istmt) ? ACL_KIND_SEQUENCE : ACL_KIND_CLASS, NameStr(pg_class_tuple->relname), 0, NULL); + /* SELinux checks */ + sepgsql_relation_grant(relOid); /* * Generate new ACL. @@ -1244,6 +1256,8 @@ ExecGrant_Database(InternalGrant *istmt) datId, grantorId, ACL_KIND_DATABASE, NameStr(pg_database_tuple->datname), 0, NULL); + /* SELinux permission checks */ + sepgsql_database_grant(datId); /* * Generate new ACL. @@ -1362,6 +1376,8 @@ ExecGrant_Fdw(InternalGrant *istmt) fdwid, grantorId, ACL_KIND_FDW, NameStr(pg_fdw_tuple->fdwname), 0, NULL); + /* SELinux permission checks */ + sepgsql_fdw_grant(fdwid); /* * Generate new ACL. @@ -1481,6 +1497,8 @@ ExecGrant_ForeignServer(InternalGrant *istmt) srvid, grantorId, ACL_KIND_FOREIGN_SERVER, NameStr(pg_server_tuple->srvname), 0, NULL); + /* SELinux checks */ + sepgsql_foreign_server_grant(srvid); /* * Generate new ACL. @@ -1599,6 +1617,8 @@ ExecGrant_Function(InternalGrant *istmt) funcId, grantorId, ACL_KIND_PROC, NameStr(pg_proc_tuple->proname), 0, NULL); + /* SELinux: db_procedure:{setattr} */ + sepgsql_proc_grant(funcId); /* * Generate new ACL. @@ -1723,6 +1743,8 @@ ExecGrant_Language(InternalGrant *istmt) langId, grantorId, ACL_KIND_LANGUAGE, NameStr(pg_language_tuple->lanname), 0, NULL); + /* SELinux checks */ + sepgsql_language_grant(langId); /* * Generate new ACL. @@ -1974,6 +1996,9 @@ ExecGrant_Namespace(InternalGrant *istmt) NameStr(pg_namespace_tuple->nspname), 0, NULL); + /* SELinux: db_schema:{setattr} */ + sepgsql_schema_grant(nspid); + /* * Generate new ACL. * diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index d168694..efb4333 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -31,6 +31,7 @@ #include "catalog/pg_database.h" #include "catalog/pg_namespace.h" #include "catalog/pg_pltemplate.h" +#include "catalog/pg_security.h" #include "catalog/pg_shdepend.h" #include "catalog/pg_shdescription.h" #include "catalog/pg_tablespace.h" @@ -304,6 +305,7 @@ IsSharedRelation(Oid relationId) relationId == AuthMemRelationId || relationId == DatabaseRelationId || relationId == PLTemplateRelationId || + relationId == SecurityRelationId || relationId == SharedDescriptionRelationId || relationId == SharedDependRelationId || relationId == TableSpaceRelationId) @@ -316,6 +318,8 @@ IsSharedRelation(Oid relationId) relationId == DatabaseNameIndexId || relationId == DatabaseOidIndexId || relationId == PLTemplateNameIndexId || + relationId == SecuritySecidIndexId || + relationId == SecuritySecattrIndexId || relationId == SharedDescriptionObjIndexId || relationId == SharedDependDependerIndexId || relationId == SharedDependReferenceIndexId || @@ -327,6 +331,8 @@ IsSharedRelation(Oid relationId) relationId == PgAuthidToastIndex || relationId == PgDatabaseToastTable || relationId == PgDatabaseToastIndex || + relationId == PgSecurityToastTable || + relationId == PgSecurityToastIndex || relationId == PgShdescriptionToastTable || relationId == PgShdescriptionToastIndex) return true; diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 416c149..4e447f0 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -64,6 +64,7 @@ #include "nodes/nodeFuncs.h" #include "parser/parsetree.h" #include "rewrite/rewriteRemove.h" +#include "security/sepgsql.h" #include "storage/lmgr.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -162,7 +163,8 @@ static void reportDependentObjects(const ObjectAddresses *targetObjects, DropBehavior behavior, int msglevel, const ObjectAddress *origObject); -static void deleteOneObject(const ObjectAddress *object, Relation depRel); +static void deleteOneObject(const ObjectAddress *object, + Relation depRel, bool permission); static void doDeletion(const ObjectAddress *object); static void AcquireDeletionLock(const ObjectAddress *object); static void ReleaseDeletionLock(const ObjectAddress *object); @@ -194,9 +196,9 @@ static void getOpFamilyDescription(StringInfo buffer, Oid opfid); * are variants on the same theme; if you change anything here you'll likely * need to fix them too. */ -void -performDeletion(const ObjectAddress *object, - DropBehavior behavior) +static void +performDeletionInternal(const ObjectAddress *object, + DropBehavior behavior, bool permission) { Relation depRel; ObjectAddresses *targetObjects; @@ -242,7 +244,7 @@ performDeletion(const ObjectAddress *object, { ObjectAddress *thisobj = targetObjects->refs + i; - deleteOneObject(thisobj, depRel); + deleteOneObject(thisobj, depRel, permission); } /* And clean up */ @@ -251,6 +253,18 @@ performDeletion(const ObjectAddress *object, heap_close(depRel, RowExclusiveLock); } +void +performDeletion(const ObjectAddress *object, DropBehavior behavior) +{ + performDeletionInternal(object, behavior, true); +} + +void +performDeletionNoPerms(const ObjectAddress *object, DropBehavior behavior) +{ + performDeletionInternal(object, behavior, false); +} + /* * performMultipleDeletions: Similar to performDeletion, but act on multiple * objects at once. @@ -324,7 +338,8 @@ performMultipleDeletions(const ObjectAddresses *objects, { ObjectAddress *thisobj = targetObjects->refs + i; - deleteOneObject(thisobj, depRel); + /* currently, all the caller path need permission checks */ + deleteOneObject(thisobj, depRel, true); } /* And clean up */ @@ -395,7 +410,7 @@ deleteWhatDependsOn(const ObjectAddress *object, if (thisextra->flags & DEPFLAG_ORIGINAL) continue; - deleteOneObject(thisobj, depRel); + deleteOneObject(thisobj, depRel, false); } /* And clean up */ @@ -945,13 +960,17 @@ reportDependentObjects(const ObjectAddresses *targetObjects, * depRel is the already-open pg_depend relation. */ static void -deleteOneObject(const ObjectAddress *object, Relation depRel) +deleteOneObject(const ObjectAddress *object, Relation depRel, bool permission) { ScanKeyData key[3]; int nkeys; SysScanDesc scan; HeapTuple tup; + /* SELinux checks db_xxx:{drop}, if necessary */ + if (permission) + sepgsql_sysobj_drop(object); + /* * First remove any pg_depend records that link from this object to * others. (Any records linking to this object should be gone already.) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 7557400..b5161c7 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -43,6 +43,7 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_security.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_type.h" @@ -56,6 +57,7 @@ #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/freespace.h" #include "storage/smgr.h" @@ -74,7 +76,8 @@ static void AddNewRelationTuple(Relation pg_class_desc, Oid new_rel_oid, Oid new_type_oid, Oid relowner, char relkind, - Datum reloptions); + Datum reloptions, + Oid *secLabels); static Oid AddNewRelationType(const char *typeName, Oid typeNamespace, Oid new_rel_oid, @@ -158,7 +161,16 @@ static FormData_pg_attribute a7 = { true, 'p', 'i', true, false, false, true, 0, {0} }; -static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7}; +/* + * System columns for enhanced security features + */ +static FormData_pg_attribute a8 = { + 0, {SecurityAttributeName}, TEXTOID, 0, -1, + SecurityAttributeNumber, 0, -1, -1, + false, 'x', 'i', true, false, false, true, 0, {0} +}; + +static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8}; /* * This function returns a Form_pg_attribute pointer for a system attribute. @@ -198,6 +210,17 @@ SystemAttributeByName(const char *attname, bool relhasoids) return NULL; } +/* + * If the given attribute number is writable, returns true. + */ +bool +SystemAttributeIsWritable(AttrNumber attnum) +{ + if (attnum == SecurityAttributeNumber) + return true; + + return false; +} /* ---------------------------------------------------------------- * XXX END OF UGLY HARD CODED BADNESS XXX @@ -293,6 +316,11 @@ heap_create(const char *relname, relid, reltablespace, shared_relation); + /* + * Does the relation have security attribute? + */ + RelationGetDescr(rel)->tdhassecid + = securityTupleDescHasSecid(relid, relkind); /* * Have the storage manager create the relation's disk file, if needed. @@ -487,7 +515,8 @@ CheckAttributeType(const char *attname, Oid atttypid) void InsertPgAttributeTuple(Relation pg_attribute_rel, Form_pg_attribute new_attribute, - CatalogIndexState indstate) + CatalogIndexState indstate, + Oid new_att_secid) { Datum values[Natts_pg_attribute]; bool nulls[Natts_pg_attribute]; @@ -520,6 +549,9 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, new_att_secid); + /* finally insert the new tuple, update the indexes, and clean up */ simple_heap_insert(pg_attribute_rel, tup); @@ -543,13 +575,15 @@ AddNewAttributeTuples(Oid new_rel_oid, TupleDesc tupdesc, char relkind, bool oidislocal, - int oidinhcount) + int oidinhcount, + Oid *secLabels) { Form_pg_attribute attr; int i; Relation rel; CatalogIndexState indstate; int natts = tupdesc->natts; + Oid new_att_secid; ObjectAddress myself, referenced; @@ -573,7 +607,11 @@ AddNewAttributeTuples(Oid new_rel_oid, attr->attstattarget = -1; attr->attcacheoff = -1; - InsertPgAttributeTuple(rel, attr, indstate); + /* Security label of the column */ + new_att_secid = (!secLabels ? InvalidOid + : secLabels[i - FirstLowInvalidHeapAttributeNumber]); + + InsertPgAttributeTuple(rel, attr, indstate, new_att_secid); /* Add dependency info */ myself.classId = RelationRelationId; @@ -601,6 +639,12 @@ AddNewAttributeTuples(Oid new_rel_oid, SysAtt[i]->attnum == ObjectIdAttributeNumber) continue; + /* skip Secid where appropriate */ + if (SysAtt[i]->attnum == SecurityAttributeNumber && + (relkind != RELKIND_RELATION || + new_rel_oid == SecurityRelationId)) + continue; + memcpy(&attStruct, (char *) SysAtt[i], sizeof(FormData_pg_attribute)); /* Fill in the correct relation OID in the copied tuple */ @@ -613,7 +657,11 @@ AddNewAttributeTuples(Oid new_rel_oid, attStruct.attinhcount = oidinhcount; } - InsertPgAttributeTuple(rel, &attStruct, indstate); + /* Security label of the system column */ + new_att_secid = (!secLabels ? InvalidOid + : secLabels[SysAtt[i]->attnum - FirstLowInvalidHeapAttributeNumber]); + + InsertPgAttributeTuple(rel, &attStruct, indstate, new_att_secid); } } @@ -641,7 +689,8 @@ void InsertPgClassTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, - Datum reloptions) + Datum reloptions, + Oid new_rel_secid) { Form_pg_class rd_rel = new_rel_desc->rd_rel; Datum values[Natts_pg_class]; @@ -690,6 +739,9 @@ InsertPgClassTuple(Relation pg_class_desc, */ HeapTupleSetOid(tup, new_rel_oid); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, new_rel_secid); + /* finally insert the new tuple, update the indexes, and clean up */ simple_heap_insert(pg_class_desc, tup); @@ -712,9 +764,11 @@ AddNewRelationTuple(Relation pg_class_desc, Oid new_type_oid, Oid relowner, char relkind, - Datum reloptions) + Datum reloptions, + Oid *secLabels) { Form_pg_class new_rel_reltup; + Oid new_rel_secid = InvalidOid; /* * first we update some of the information in our uncataloged relation's @@ -771,8 +825,12 @@ AddNewRelationTuple(Relation pg_class_desc, new_rel_desc->rd_att->tdtypeid = new_type_oid; + if (secLabels) + new_rel_secid = secLabels[0]; + /* Now build and insert the tuple */ - InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid, reloptions); + InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid, + reloptions, new_rel_secid); } @@ -843,7 +901,8 @@ heap_create_with_catalog(const char *relname, int oidinhcount, OnCommitAction oncommit, Datum reloptions, - bool allow_system_table_mods) + bool allow_system_table_mods, + Oid *secLabels) { Relation pg_class_desc; Relation new_rel_desc; @@ -1019,13 +1078,14 @@ heap_create_with_catalog(const char *relname, new_type_oid, ownerid, relkind, - reloptions); + reloptions, + secLabels); /* * now add tuples to pg_attribute for the attributes in our new relation. */ AddNewAttributeTuples(relid, new_rel_desc->rd_att, relkind, - oidislocal, oidinhcount); + oidislocal, oidinhcount, secLabels); /* * Make a dependency link to force the relation to be deleted if its @@ -1484,6 +1544,11 @@ heap_drop_with_catalog(Oid relid) * delete relation tuple */ DeleteRelationTuple(relid); + + /* + * delete orphan pg_security entries + */ + securityReclaimOnDropTable(relid); } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index ad679ea..828d51a 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -48,6 +48,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/var.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/procarray.h" @@ -352,7 +353,8 @@ AppendAttributeTuples(Relation indexRelation, int numatts) Assert(indexTupDesc->attrs[i]->attnum == i + 1); Assert(indexTupDesc->attrs[i]->attcacheoff == -1); - InsertPgAttributeTuple(pg_attribute, indexTupDesc->attrs[i], indstate); + InsertPgAttributeTuple(pg_attribute, indexTupDesc->attrs[i], + indstate, InvalidOid); } CatalogCloseIndexes(indstate); @@ -653,7 +655,7 @@ index_create(Oid heapRelationId, */ InsertPgClassTuple(pg_class, indexRelation, RelationGetRelid(indexRelation), - reloptions); + reloptions, InvalidOid); /* done with pg_class */ heap_close(pg_class, RowExclusiveLock); diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 2b0cb35..64de050 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -39,6 +39,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_func.h" +#include "security/sepgsql.h" #include "storage/backendid.h" #include "storage/ipc.h" #include "utils/acl.h" @@ -2105,7 +2106,10 @@ LookupExplicitNamespace(const char *nspname) if (strcmp(nspname, "pg_temp") == 0) { if (OidIsValid(myTempNamespace)) + { + sepgsql_schema_search(myTempNamespace, true); return myTempNamespace; + } /* * Since this is used only for looking up existing objects, there is @@ -2127,6 +2131,7 @@ LookupExplicitNamespace(const char *nspname) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, nspname); + sepgsql_schema_search(namespaceId, true); return namespaceId; } @@ -2722,7 +2727,8 @@ recomputeNamespacePath(void) if (OidIsValid(namespaceId) && !list_member_oid(oidlist, namespaceId) && pg_namespace_aclcheck(namespaceId, roleid, - ACL_USAGE) == ACLCHECK_OK) + ACL_USAGE) == ACLCHECK_OK && + sepgsql_schema_search(namespaceId, false)) oidlist = lappend_oid(oidlist, namespaceId); } } @@ -2731,7 +2737,8 @@ recomputeNamespacePath(void) /* pg_temp --- substitute temp namespace, if any */ if (OidIsValid(myTempNamespace)) { - if (!list_member_oid(oidlist, myTempNamespace)) + if (!list_member_oid(oidlist, myTempNamespace) && + sepgsql_schema_search(myTempNamespace, false)) oidlist = lappend_oid(oidlist, myTempNamespace); } else @@ -2750,7 +2757,8 @@ recomputeNamespacePath(void) if (OidIsValid(namespaceId) && !list_member_oid(oidlist, namespaceId) && pg_namespace_aclcheck(namespaceId, roleid, - ACL_USAGE) == ACLCHECK_OK) + ACL_USAGE) == ACLCHECK_OK && + sepgsql_schema_search(namespaceId, false)) oidlist = lappend_oid(oidlist, namespaceId); } } @@ -2816,6 +2824,7 @@ InitTempTableNamespace(void) char namespaceName[NAMEDATALEN]; Oid namespaceId; Oid toastspaceId; + Oid nspsecid; Assert(!OidIsValid(myTempNamespace)); @@ -2836,6 +2845,9 @@ InitTempTableNamespace(void) errmsg("permission denied to create temporary tables in database \"%s\"", get_database_name(MyDatabaseId)))); + /* SELinux checks permission to create temp schema */ + nspsecid = sepgsql_schema_create(namespaceName, true, NULL); + snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId); namespaceId = GetSysCacheOid(NAMESPACENAME, @@ -2851,7 +2863,9 @@ InitTempTableNamespace(void) * temp tables. This works because the places that access the temp * namespace for my own backend skip permissions checks on it. */ - namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID); + namespaceId = NamespaceCreate(namespaceName, + BOOTSTRAP_SUPERUSERID, + nspsecid); /* Advance command counter to make namespace visible */ CommandCounterIncrement(); } @@ -2877,7 +2891,9 @@ InitTempTableNamespace(void) 0, 0, 0); if (!OidIsValid(toastspaceId)) { - toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID); + toastspaceId = NamespaceCreate(namespaceName, + BOOTSTRAP_SUPERUSERID, + nspsecid); /* Advance command counter to make namespace visible */ CommandCounterIncrement(); } @@ -3030,6 +3046,13 @@ RemoveTempRelations(Oid tempNamespaceId) object.objectId = tempNamespaceId; object.objectSubId = 0; + /* + * TODO: + * SELinux should not check db_xxx:{drop} permission during cleaning + * up all the temporary objects. It may be necessary a bool argument + * to control MAC permission check on deleteOneObject() called from + * deleteWhatDependsOn() and so on. + */ deleteWhatDependsOn(&object, false); } diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 845322e..cc1c59a 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -231,7 +231,8 @@ AggregateCreate(const char *aggName, NIL, /* parameterDefaults */ PointerGetDatum(NULL), /* proconfig */ 1, /* procost */ - 0); /* prorows */ + 0, /* prorows */ + NULL); /* proseclabel*/ /* * Okay to create the pg_aggregate entry. diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c index d4a8183..30f2604 100644 --- a/src/backend/catalog/pg_conversion.c +++ b/src/backend/catalog/pg_conversion.c @@ -40,7 +40,7 @@ Oid ConversionCreate(const char *conname, Oid connamespace, Oid conowner, int32 conforencoding, int32 contoencoding, - Oid conproc, bool def) + Oid conproc, Oid consecid, bool def) { int i; Relation rel; @@ -104,6 +104,8 @@ ConversionCreate(const char *conname, Oid connamespace, values[Anum_pg_conversion_condefault - 1] = BoolGetDatum(def); tup = heap_form_tuple(tupDesc, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, consecid); /* insert a new tuple */ oid = simple_heap_insert(rel, tup); diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c index 54d992f..67faa30 100644 --- a/src/backend/catalog/pg_largeobject.c +++ b/src/backend/catalog/pg_largeobject.c @@ -25,6 +25,7 @@ #include "catalog/pg_largeobject_metadata.h" #include "catalog/toasting.h" #include "miscadmin.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -40,7 +41,7 @@ * will appear to exist with size 0. */ Oid -LargeObjectCreate(Oid loid) +LargeObjectCreate(Oid loid, Oid secid) { Relation pg_lo_meta; HeapTuple ntup; @@ -65,6 +66,8 @@ LargeObjectCreate(Oid loid) values, nulls); if (OidIsValid(loid)) HeapTupleSetOid(ntup, loid); + if (HeapTupleHasSecid(ntup)) + HeapTupleSetSecid(ntup, secid); loid_new = simple_heap_insert(pg_lo_meta, ntup); Assert(!OidIsValid(loid) || loid == loid_new); @@ -205,6 +208,9 @@ LargeObjectAlterOwner(Oid loid, Oid newOwnerId) /* Must be able to become new owner */ check_is_member_of_role(GetUserId(), newOwnerId); + + /* SELinux: db_blob:{setattr} */ + sepgsql_largeobject_alter(loid); } memset(values, 0, sizeof(values)); diff --git a/src/backend/catalog/pg_namespace.c b/src/backend/catalog/pg_namespace.c index 8fac0b6..62802a9 100644 --- a/src/backend/catalog/pg_namespace.c +++ b/src/backend/catalog/pg_namespace.c @@ -28,7 +28,7 @@ * --------------- */ Oid -NamespaceCreate(const char *nspName, Oid ownerId) +NamespaceCreate(const char *nspName, Oid ownerId, Oid nspsecid) { Relation nspdesc; HeapTuple tup; @@ -66,6 +66,8 @@ NamespaceCreate(const char *nspName, Oid ownerId) tupDesc = nspdesc->rd_att; tup = heap_form_tuple(tupDesc, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, nspsecid); nspoid = simple_heap_insert(nspdesc, tup); Assert(OidIsValid(nspoid)); diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c index af307b7..6fe025c 100644 --- a/src/backend/catalog/pg_operator.c +++ b/src/backend/catalog/pg_operator.c @@ -28,6 +28,7 @@ #include "catalog/pg_type.h" #include "miscadmin.h" #include "parser/parse_oper.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -204,6 +205,7 @@ OperatorShellMake(const char *operatorName, { Relation pg_operator_desc; Oid operatorObjectId; + Oid secid; int i; HeapTuple tup; Datum values[Natts_pg_operator]; @@ -220,6 +222,10 @@ OperatorShellMake(const char *operatorName, errmsg("\"%s\" is not a valid operator name", operatorName))); + /* SELinux permission check */ + secid = sepgsql_operator_create(operatorName, InvalidOid, + operatorNamespace, + InvalidOid, InvalidOid, InvalidOid); /* * initialize our *nulls and *values arrays */ @@ -260,6 +266,8 @@ OperatorShellMake(const char *operatorName, * create a new operator tuple */ tup = heap_form_tuple(tupDesc, values, nulls); + if (HeapTupleHasSecid(tup) && OidIsValid(secid)) + HeapTupleSetSecid(tup, secid); /* * insert our "shell" operator tuple @@ -347,6 +355,7 @@ OperatorCreate(const char *operatorName, bool selfCommutator = false; NameData oname; TupleDesc tupDesc; + Oid secid; int i; /* @@ -476,6 +485,10 @@ OperatorCreate(const char *operatorName, else negatorId = InvalidOid; + /* SELinux permission checks */ + secid = sepgsql_operator_create(operatorName, operatorObjectId, + operatorNamespace, + procedureId, restrictionId, joinId); /* * set up values in the operator tuple */ @@ -523,6 +536,8 @@ OperatorCreate(const char *operatorName, values, nulls, replaces); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, secid); simple_heap_update(pg_operator_desc, &tup->t_self, tup); } @@ -530,6 +545,8 @@ OperatorCreate(const char *operatorName, { tupDesc = pg_operator_desc->rd_att; tup = heap_form_tuple(tupDesc, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, secid); operatorObjectId = simple_heap_insert(pg_operator_desc, tup); } diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 2980d2b..55e66e5 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -29,6 +29,7 @@ #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "parser/parse_type.h" +#include "security/sepgsql.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "utils/acl.h" @@ -78,7 +79,8 @@ ProcedureCreate(const char *procedureName, List *parameterDefaults, Datum proconfig, float4 procost, - float4 prorows) + float4 prorows, + Node *proseclabel) { Oid retval; int parameterCount; @@ -97,6 +99,7 @@ ProcedureCreate(const char *procedureName, Datum values[Natts_pg_proc]; bool replaces[Natts_pg_proc]; Oid relid; + Oid prosecid = InvalidOid; NameData procname; TupleDesc tupDesc; bool is_update; @@ -344,6 +347,11 @@ ProcedureCreate(const char *procedureName, ObjectIdGetDatum(procNamespace), 0); + /* Check permission to create/replace a function */ + prosecid = sepgsql_proc_create(procedureName, oldtup, + procNamespace, languageObjectId, + (DefElem *)proseclabel); + if (HeapTupleIsValid(oldtup)) { /* There is one; okay to replace it? */ @@ -481,6 +489,8 @@ ProcedureCreate(const char *procedureName, /* Okay, do it... */ tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, prosecid); simple_heap_update(rel, &tup->t_self, tup); ReleaseSysCache(oldtup); @@ -490,6 +500,8 @@ ProcedureCreate(const char *procedureName, { /* Creating a new procedure */ tup = heap_form_tuple(tupDesc, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, prosecid); simple_heap_insert(rel, tup); is_update = false; } diff --git a/src/backend/catalog/pg_security.c b/src/backend/catalog/pg_security.c new file mode 100644 index 0000000..0db05e2 --- /dev/null +++ b/src/backend/catalog/pg_security.c @@ -0,0 +1,483 @@ +/* + * src/backend/catalog/pg_security.c + * routines to support security label management + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/sysattr.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_security.h" +#include "catalog/pg_type.h" +#include "executor/spi.h" +#include "miscadmin.h" +#include "security/rowlevel.h" +#include "security/sepgsql.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/tqual.h" + +bool +securityTupleDescHasSecid(Oid relid, char relkind) +{ + return sepgsqlTupleDescHasSecid(relid, relkind); +} + +/* + * securityOnCreateDatabase + * copies all the entries refered by source database + */ +void +securityOnCreateDatabase(Oid src_datid, Oid dst_datid) +{ + Relation rel; + ScanKeyData keys[1]; + SysScanDesc scan; + HeapTuple oldtup, newtup; + Datum values[Natts_pg_security]; + bool nulls[Natts_pg_security]; + bool replaces[Natts_pg_security]; + + /* Scan all entries with pg_security.datid = src_datid */ + ScanKeyInit(&keys[0], + Anum_pg_security_datid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(src_datid)); + + rel = heap_open(SecurityRelationId, RowExclusiveLock); + + scan = systable_beginscan(rel, SecuritySecidIndexId, true, + SnapshotNow, 1, keys); + + /* pg_security.datid shall be replaced */ + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + memset(replaces, false, sizeof(replaces)); + + values[Anum_pg_security_datid - 1] = ObjectIdGetDatum(dst_datid); + replaces[Anum_pg_security_datid - 1] = true; + + while (HeapTupleIsValid(oldtup = systable_getnext(scan))) + { + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + values, nulls, replaces); + simple_heap_insert(rel, newtup); + + CatalogUpdateIndexes(rel, newtup); + + heap_freetuple(newtup); + } + systable_endscan(scan); + + heap_close(rel, RowExclusiveLock); +} + +/* + * securityOnDropDatabase + * drops all the entries refered by dropped database + */ +void +securityOnDropDatabase(Oid datid) +{ + Relation rel; + ScanKeyData keys[1]; + SysScanDesc scan; + HeapTuple tuple; + + /* Scan all entries with pg_security.datid = datid */ + ScanKeyInit(&keys[0], + Anum_pg_security_datid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(datid)); + + rel = heap_open(SecurityRelationId, RowExclusiveLock); + + scan = systable_beginscan(rel, SecuritySecidIndexId, true, + SnapshotNow, 1, keys); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + simple_heap_delete(rel, &tuple->t_self); + } + + systable_endscan(scan); + + heap_close(rel, RowExclusiveLock); +} + +/* + * InputSecurityAttr + */ +static Oid +InputSecurityAttr(Oid relid, const char *secattr) +{ + LOCKMODE lockmode = AccessShareLock; + Relation rel; + ScanKeyData skey[3]; + SysScanDesc scan; + HeapTuple tuple; + Oid datid; + Oid secid; + Datum values[Natts_pg_security]; + bool nulls[Natts_pg_security]; + + datid = (IsSharedRelation(relid) ? InvalidOid : MyDatabaseId); + +retry: + /* + * Lookup pg_security catalog first + */ + rel = heap_open(SecurityRelationId, lockmode); + + ScanKeyInit(&skey[0], + Anum_pg_security_datid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(datid)); + ScanKeyInit(&skey[1], + Anum_pg_security_relid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + ScanKeyInit(&skey[2], + Anum_pg_security_secattr, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(secattr)); + + scan = systable_beginscan(rel, SecuritySecattrIndexId, true, + SnapshotToast, 3, skey); + + tuple = systable_getnext(scan); + if (HeapTupleIsValid(tuple)) + { + secid = ((Form_pg_security) GETSTRUCT(tuple))->secid; + + systable_endscan(scan); + + heap_close(rel, lockmode); + + return secid; + } + + systable_endscan(scan); + + /* + * If not exist, try to insert a new entry. + */ + if (lockmode == AccessShareLock) + { + heap_close(rel, lockmode); + + lockmode = RowExclusiveLock; + + goto retry; + } + + memset(nulls, false, sizeof(nulls)); + secid = GetNewOidWithIndex(rel, SecuritySecidIndexId, + Anum_pg_security_secid); + values[Anum_pg_security_secid - 1] = ObjectIdGetDatum(secid); + values[Anum_pg_security_datid - 1] = ObjectIdGetDatum(datid); + values[Anum_pg_security_relid - 1] = ObjectIdGetDatum(relid); + values[Anum_pg_security_secattr - 1] = CStringGetTextDatum(secattr); + + tuple = heap_form_tuple(RelationGetDescr(rel), values, nulls); + + simple_heap_insert(rel, tuple); + + CatalogUpdateIndexes(rel, tuple); + + heap_close(rel, lockmode); + + return secid; +} + +static char * +OutputSecurityAttr(Oid relid, Oid secid) +{ + Relation rel; + ScanKeyData skey[3]; + SysScanDesc scan; + HeapTuple tuple; + Oid datid; + char *result = NULL; + + datid = (IsSharedRelation(relid) ? InvalidOid : MyDatabaseId); + + /* + * Lookup pg_security catalog first + */ + rel = heap_open(SecurityRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_security_secid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(secid)); + ScanKeyInit(&skey[1], + Anum_pg_security_datid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(datid)); + ScanKeyInit(&skey[2], + Anum_pg_security_relid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + scan = systable_beginscan(rel, SecuritySecidIndexId, true, + SnapshotToast, 3, skey); + + tuple = systable_getnext(scan); + if (HeapTupleIsValid(tuple)) + { + Datum datum; + bool isnull; + + datum = heap_getattr(tuple, + Anum_pg_security_secattr, + RelationGetDescr(rel), &isnull); + if (!isnull) + result = TextDatumGetCString(datum); + } + + systable_endscan(scan); + + heap_close(rel, AccessShareLock); + + return result; +} + +/* + * input/output handler + */ +Oid +securityRawSecLabelIn(Oid relid, char *seclabel) +{ + seclabel = sepgsqlRawSecLabelIn(seclabel); + + return InputSecurityAttr(relid, seclabel); +} + +char * +securityRawSecLabelOut(Oid relid, Oid secid) +{ + char *seclabel = OutputSecurityAttr(relid, secid); + + return sepgsqlRawSecLabelOut(seclabel); +} + +Oid +securityTransSecLabelIn(Oid relid, char *seclabel) +{ + seclabel = sepgsqlTransSecLabelIn(seclabel); + + return securityRawSecLabelIn(relid, seclabel); +} + +char * +securityTransSecLabelOut(Oid relid, Oid secid) +{ + char *seclabel = securityRawSecLabelOut(relid, secid); + + return sepgsqlTransSecLabelOut(seclabel); +} + +/* + * Output handler for system columns + */ +Datum +securitySysattSecLabelOut(Oid relid, HeapTuple tuple) +{ + char *seclabel; + + seclabel = sepgsqlSysattSecLabelOut(relid, tuple); + if (!seclabel) + seclabel = "unlabled"; + + return CStringGetTextDatum(seclabel); +} + +/* + * securityReclaimOnDropTable + * drop orphan entries within pg_security on drop table + */ +void +securityReclaimOnDropTable(Oid relid) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[2]; + HeapTuple tuple; + Oid database_oid; + + database_oid = (IsSharedRelation(relid) ? InvalidOid : MyDatabaseId); + ScanKeyInit(&key[0], + Anum_pg_security_datid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(database_oid)); + ScanKeyInit(&key[1], + Anum_pg_security_relid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + rel = heap_open(SecurityRelationId, RowExclusiveLock); + scan = systable_beginscan(rel, SecuritySecattrIndexId, true, + SnapshotNow, 2, key); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + simple_heap_delete(rel, &tuple->t_self); + + systable_endscan(scan); + + heap_close(rel, RowExclusiveLock); +} + +/* + * security_quote_relation + * returns palloc'de identifier with explicit namespace + */ +static char * +security_quote_relation(Oid relid) +{ + Oid nspoid = get_rel_namespace(relid); + char *nspname; + char *relname; + + nspname = get_namespace_name(nspoid); + relname = get_rel_name(relid); + + return quote_qualified_identifier(nspname, relname); +} + +/* + * security_reclaim_table + * reclaims orphan entries associated to a certain table + */ +static int +seclabelRelationReclaimExec(Oid relOid) +{ + StringInfoData query; + SPIPlanPtr plan; + Oid types[2]; + Datum values[2]; + Oid proc_oid; + Oid database_oid; + char *relname_full; + char *attname_datid; + char *attname_relid; + char *attname_secid; + char *attname_seckind; + char *attname_secattr; + char *sec_proname; + char *sec_nspname; + Form_pg_proc proForm; + HeapTuple protup; + + /* + * LOCK the target table + */ + initStringInfo(&query); + relname_full = security_quote_relation(relOid); + appendStringInfo(&query, "LOCK %s IN SHARE MODE", relname_full); + if (SPI_execute(query.data, false, 0) != SPI_OK_UTILITY) + elog(ERROR, "SPI_execute failed on %s", query.data); + + /* + * DELETE orphan entries + */ + initStringInfo(&query); + attname_secid = get_attname(SecurityRelationId, Anum_pg_security_secid); + attname_datid = get_attname(SecurityRelationId, Anum_pg_security_datid); + attname_relid = get_attname(SecurityRelationId, Anum_pg_security_relid); + attname_secattr = get_attname(SecurityRelationId, Anum_pg_security_secattr); + + appendStringInfo(&query, + "DELETE FROM %s " + "WHERE %s = $1 AND %s = $2 AND %s NOT IN ", + security_quote_relation(SecurityRelationId), + quote_identifier(attname_datid), + quote_identifier(attname_relid), + quote_identifier(attname_secid)); + + protup = SearchSysCache(PROCOID, + ObjectIdGetDatum(F_SECLABEL_TO_SECID), + 0, 0, 0); + if (!HeapTupleIsValid(protup)) + elog(ERROR, "cache lookup failed for procedure: %u", F_SECLABEL_TO_SECID); + + proForm = (Form_pg_proc) GETSTRUCT(protup); + sec_proname = NameStr(proForm->proname); + sec_nspname = get_namespace_name(proForm->pronamespace); + + appendStringInfo(&query, + "(SELECT %s.%s(%s) FROM ONLY %s)", + quote_identifier(sec_nspname), + quote_identifier(sec_proname), + quote_identifier(get_rel_name(relOid)), + relname_full); + ReleaseSysCache(protup); + + /* + * Setup and execute query + */ + types[0] = OIDOID; + types[1] = OIDOID; + plan = SPI_prepare(query.data, 2, types); + if (!plan) + elog(ERROR, "SPI_prepare failed on %s", query.data); + + database_oid = (IsSharedRelation(relOid) ? InvalidOid : MyDatabaseId); + + values[0] = ObjectIdGetDatum(database_oid); + values[1] = ObjectIdGetDatum(relOid); + if (SPI_execute_plan(plan, values, NULL, false, 0) != SPI_OK_DELETE) + elog(ERROR, "SPI_execute_plan failed on %s", query.data); + + SPI_freetuptable(SPI_tuptable); + + return SPI_processed; +} + +void +seclabelRelationReclaim(Oid relOid) +{ + int save_mode; + + if (!superuser() || + get_rel_relkind(relOid) != RELKIND_RELATION) + return; + + save_mode = sepostgresql_mode; + sepostgresql_mode = SEPGSQL_MODE_INTERNAL; + PG_TRY(); + { + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + seclabelRelationReclaimExec(relOid); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + } + PG_CATCH(); + { + sepostgresql_mode = save_mode; + PG_RE_THROW(); + } + PG_END_TRY(); + sepostgresql_mode = save_mode; +} + +Datum +seclabel_to_secid(PG_FUNCTION_ARGS) +{ + HeapTupleHeader tuphdr = PG_GETARG_HEAPTUPLEHEADER(0); + + PG_RETURN_OID(HeapTupleHeaderGetSecid(tuphdr)); +} diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index 451724f..b95b414 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -37,6 +37,7 @@ #include "commands/schemacmds.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" +#include "security/sepgsql.h" #include "storage/lmgr.h" #include "miscadmin.h" #include "utils/acl.h" @@ -1340,6 +1341,8 @@ shdepReassignOwned(List *roleids, Oid newrole) break; case TypeRelationId: + /* SELinux checks */ + sepgsql_type_alter(sdepForm->objid, NULL, InvalidOid); AlterTypeOwnerInternal(sdepForm->objid, newrole, true); break; @@ -1352,7 +1355,8 @@ shdepReassignOwned(List *roleids, Oid newrole) break; case RelationRelationId: - + /* SELinux checks */ + sepgsql_relation_alter(sdepForm->objid, NULL, InvalidOid); /* * Pass recursing = true so that we don't fail on indexes, * owned sequences, etc when we happen to visit them diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 7696480..4586056 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -25,6 +25,7 @@ #include "commands/typecmds.h" #include "miscadmin.h" #include "parser/scansup.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -56,10 +57,17 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) Datum values[Natts_pg_type]; bool nulls[Natts_pg_type]; Oid typoid; + Oid typsid; NameData name; Assert(PointerIsValid(typeName)); + /* SELinux check permission to create a shell type */ + typsid = sepgsql_type_create(typeName, InvalidOid, typeNamespace, + F_SHELL_IN, F_SHELL_OUT, + InvalidOid, InvalidOid, + InvalidOid, InvalidOid, InvalidOid); + /* * open pg_type */ @@ -201,6 +209,7 @@ TypeCreate(Oid newTypeOid, { Relation pg_type_desc; Oid typeObjectId; + Oid typeSecid = InvalidOid; bool rebuildDeps = false; HeapTuple tup; bool nulls[Natts_pg_type]; @@ -367,6 +376,15 @@ TypeCreate(Oid newTypeOid, CStringGetDatum(typeName), ObjectIdGetDatum(typeNamespace), 0, 0); + + /* SELinux checks to create/replace type */ + if (!isImplicitArray && typeType != TYPTYPE_COMPOSITE) + typeSecid = sepgsql_type_create(typeName, tup, typeNamespace, + inputProcedure, outputProcedure, + receiveProcedure, sendProcedure, + typmodinProcedure, typmodoutProcedure, + analyzeProcedure); + if (HeapTupleIsValid(tup)) { /* @@ -412,6 +430,8 @@ TypeCreate(Oid newTypeOid, /* Force the OID if requested by caller, else heap_insert does it */ if (OidIsValid(newTypeOid)) HeapTupleSetOid(tup, newTypeOid); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, typeSecid); typeObjectId = simple_heap_insert(pg_type_desc, tup); } diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 6e7b5cf..10ea3a2 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -28,6 +28,7 @@ #include "catalog/toasting.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "security/sepgsql.h" #include "utils/builtins.h" #include "utils/syscache.h" @@ -125,6 +126,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, char toast_relname[NAMEDATALEN]; char toast_idxname[NAMEDATALEN]; IndexInfo *indexInfo; + Oid *secLabels; Oid classObjectId[2]; int16 coloptions[2]; ObjectAddress baseobject, @@ -199,6 +201,11 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, else namespaceid = PG_TOAST_NAMESPACE; + secLabels = sepgsql_relation_create(toast_relname, + RELKIND_TOASTVALUE, + tupdesc, namespaceid, + NULL, NIL, false, false); + toast_relid = heap_create_with_catalog(toast_relname, namespaceid, rel->rd_rel->reltablespace, @@ -212,7 +219,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, 0, ONCOMMIT_NOOP, reloptions, - true); + true, + secLabels); /* make the toast relation visible, else index creation will fail */ CommandCounterIncrement(); diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c index fd3f336..dfca678 100644 --- a/src/backend/commands/aggregatecmds.c +++ b/src/backend/commands/aggregatecmds.c @@ -32,6 +32,7 @@ #include "miscadmin.h" #include "parser/parse_func.h" #include "parser/parse_type.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -311,6 +312,9 @@ RenameAggregate(List *name, List *args, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* SELinux permission checks */ + sepgsql_proc_alter(procOid, newname, InvalidOid); + /* rename */ namestrcpy(&(((Form_pg_proc) GETSTRUCT(tup))->proname), newname); simple_heap_update(rel, &tup->t_self, tup); diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 46bc4df..79131d5 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -289,3 +289,32 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt) (int) stmt->objectType); } } + +void +ExecAlterSecLabelStmt(AlterSecLabelStmt *stmt) +{ + DefElem *seclabel = (DefElem *)stmt->secLabel; + + switch (stmt->objectType) + { + case OBJECT_DATABASE: + AlterDatabaseSecLabel(strVal(linitial(stmt->object)), seclabel); + break; + case OBJECT_SCHEMA: + AlterSchemaSecLabel(strVal(linitial(stmt->object)), seclabel); + break; + case OBJECT_TABLE: + case OBJECT_SEQUENCE: + case OBJECT_COLUMN: + CheckRelationOwnership(stmt->relation, true); + AlterRelationSecLabel(stmt->relation, stmt->subname, + stmt->objectType, seclabel); + break; + case OBJECT_FUNCTION: + AlterFunctionSecLabel(stmt->object, stmt->objarg, seclabel); + break; + default: + elog(ERROR, "unrecognized AlterSecLabelStmt type: %d", + (int) stmt->objectType); + } +} diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index a6ba2ec..b990a33 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -36,6 +36,7 @@ #include "commands/trigger.h" #include "commands/vacuum.h" #include "miscadmin.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/procarray.h" #include "utils/acl.h" @@ -617,8 +618,9 @@ rebuild_relation(Relation OldHeap, Oid indexOid) /* * The new relation is local to our transaction and we know nothing * depends on it, so DROP_RESTRICT should be OK. + * SELinux does not check any permissions here. */ - performDeletion(&object, DROP_RESTRICT); + performDeletionNoPerms(&object, DROP_RESTRICT); /* performDeletion does CommandCounterIncrement at end */ @@ -712,7 +714,8 @@ make_new_heap(Oid OIDOldHeap, const char *NewName, Oid NewTableSpace) 0, ONCOMMIT_NOOP, reloptions, - allowSystemTableMods); + allowSystemTableMods, + sepgsql_relation_copy(OldHeap)); ReleaseSysCache(tuple); @@ -924,6 +927,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) if (NewHeap->rd_rel->relhasoids) HeapTupleSetOid(copiedTuple, HeapTupleGetOid(tuple)); + /* Preserve SID, if any */ + if (HeapTupleHasSecid(copiedTuple)) + HeapTupleSetSecid(copiedTuple, HeapTupleGetSecid(tuple)); + /* The heap rewrite module does the rest */ rewrite_heap_tuple(rwstate, tuple, copiedTuple); diff --git a/src/backend/commands/conversioncmds.c b/src/backend/commands/conversioncmds.c index 045ffca..97914d5 100644 --- a/src/backend/commands/conversioncmds.c +++ b/src/backend/commands/conversioncmds.c @@ -24,6 +24,7 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" #include "parser/parse_func.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -45,6 +46,7 @@ CreateConversionCommand(CreateConversionStmt *stmt) int from_encoding; int to_encoding; Oid funcoid; + Oid secid; const char *from_encoding_name = stmt->for_encoding_name; const char *to_encoding_name = stmt->to_encoding_name; List *func_name = stmt->func_name; @@ -96,6 +98,9 @@ CreateConversionCommand(CreateConversionStmt *stmt) aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(func_name)); + /* SELinux checks */ + secid = sepgsql_conversion_create(conversion_name, namespaceId, funcoid); + /* * Check that the conversion function is suitable for the requested source * and target encodings. We do that by calling the function with an empty @@ -114,7 +119,7 @@ CreateConversionCommand(CreateConversionStmt *stmt) * name) */ ConversionCreate(conversion_name, namespaceId, GetUserId(), - from_encoding, to_encoding, funcoid, stmt->def); + from_encoding, to_encoding, funcoid, secid, stmt->def); } /* @@ -240,6 +245,9 @@ RenameConversion(List *name, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* SELinux checks */ + sepgsql_conversion_alter(conversionOid, newname); + /* rename */ namestrcpy(&(((Form_pg_conversion) GETSTRUCT(tup))->conname), newname); simple_heap_update(rel, &tup->t_self, tup); @@ -336,6 +344,8 @@ AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(convForm->connamespace)); } + /* SELinux checks */ + sepgsql_conversion_alter(HeapTupleGetOid(tup), NULL); /* * Modify the owner --- okay to scribble on tup because it's a copy diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index a151999..ee7344d 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -21,8 +21,11 @@ #include #include "access/heapam.h" +#include "access/sysattr.h" #include "access/xact.h" +#include "catalog/heap.h" #include "catalog/namespace.h" +#include "catalog/pg_security.h" #include "catalog/pg_type.h" #include "commands/copy.h" #include "commands/trigger.h" @@ -34,6 +37,8 @@ #include "optimizer/planner.h" #include "parser/parse_relation.h" #include "rewrite/rewriteHandler.h" +#include "security/rowlevel.h" +#include "security/sepgsql.h" #include "storage/fd.h" #include "tcop/tcopprot.h" #include "utils/acl.h" @@ -160,6 +165,10 @@ typedef struct CopyStateData char *raw_buf; int raw_buf_index; /* next byte to process */ int raw_buf_len; /* total # of bytes stored */ + + /* dump/restore support for security_label */ + FmgrInfo seclabel_out_function; + bool seclabel_force_quot; } CopyStateData; typedef CopyStateData *CopyState; @@ -243,8 +252,8 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0"; /* non-export function prototypes */ static void DoCopyTo(CopyState cstate); static void CopyTo(CopyState cstate); -static void CopyOneRowTo(CopyState cstate, Oid tupleOid, - Datum *values, bool *nulls); +static void CopyOneRowTo(CopyState cstate, HeapTuple tuple, + Datum *values, bool *nulls); static void CopyFrom(CopyState cstate); static bool CopyReadLine(CopyState cstate); static bool CopyReadLineText(CopyState cstate); @@ -958,12 +967,19 @@ DoCopy(const CopyStmt *stmt, const char *queryString) errmsg("CSV quote character must not appear in the NULL specification"))); /* Disallow file COPY except to superusers. */ - if (!pipe && !superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to COPY to or from a file"), - errhint("Anyone can COPY to stdout or from stdin. " - "psql's \\copy command also works for anyone."))); + if (!pipe) + { + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to COPY to or from a file"), + errhint("Anyone can COPY to stdout or from stdin. " + "psql's \\copy command also works for anyone."))); + if (is_from) + sepgsql_file_read(stmt->filename); + else + sepgsql_file_write(stmt->filename); + } if (stmt->relation) { @@ -1090,6 +1106,9 @@ DoCopy(const CopyStmt *stmt, const char *queryString) num_phys_attrs = tupDesc->natts; + /* SELinux: check table/column level permission */ + sepgsqlCheckCopyTable(cstate->rel, cstate->attnumlist, is_from); + /* Convert FORCE QUOTE name list to per-column flags, check validity */ cstate->force_quote_flags = (bool *) palloc0(num_phys_attrs * sizeof(bool)); if (force_quote) @@ -1104,11 +1123,31 @@ DoCopy(const CopyStmt *stmt, const char *queryString) int attnum = lfirst_int(cur); if (!list_member_int(cstate->attnumlist, attnum)) + { + Form_pg_attribute attForm; + + if (SystemAttributeIsWritable(attnum)) + attForm = SystemAttributeDefinition(attnum, true); + else + attForm = tupDesc->attrs[attnum - 1]; + + Assert(attForm != NULL); + ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("FORCE QUOTE column \"%s\" not referenced by COPY", - NameStr(tupDesc->attrs[attnum - 1]->attname)))); - cstate->force_quote_flags[attnum - 1] = true; + NameStr(attForm->attname)))); + } + + switch (attnum) + { + case SecurityAttributeNumber: + cstate->seclabel_force_quot = true; + break; + default: + cstate->force_quote_flags[attnum - 1] = true; + break; + } } } @@ -1126,10 +1165,23 @@ DoCopy(const CopyStmt *stmt, const char *queryString) int attnum = lfirst_int(cur); if (!list_member_int(cstate->attnumlist, attnum)) + { + Form_pg_attribute attForm; + + if (SystemAttributeIsWritable(attnum)) + attForm = SystemAttributeDefinition(attnum, true); + else + attForm = tupDesc->attrs[attnum - 1]; + + Assert(attForm != NULL); + ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("FORCE NOT NULL column \"%s\" not referenced by COPY", - NameStr(tupDesc->attrs[attnum - 1]->attname)))); + NameStr(attForm->attname)))); + } + if (SystemAttributeIsWritable(attnum)) + continue; /* ignore, if specified */ cstate->force_notnull_flags[attnum - 1] = true; } } @@ -1321,16 +1373,31 @@ CopyTo(CopyState cstate) int attnum = lfirst_int(cur); Oid out_func_oid; bool isvarlena; + FmgrInfo *out_fmgr; + Form_pg_attribute attForm; + + switch (attnum) + { + case SecurityAttributeNumber: + attForm = SystemAttributeDefinition(attnum, true); + out_fmgr = &cstate->seclabel_out_function; + break; + + default: + attForm = attr[attnum - 1]; + out_fmgr = &cstate->out_functions[attnum - 1]; + break; + } if (cstate->binary) - getTypeBinaryOutputInfo(attr[attnum - 1]->atttypid, + getTypeBinaryOutputInfo(attForm->atttypid, &out_func_oid, &isvarlena); else - getTypeOutputInfo(attr[attnum - 1]->atttypid, + getTypeOutputInfo(attForm->atttypid, &out_func_oid, &isvarlena); - fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + fmgr_info(out_func_oid, out_fmgr); } /* @@ -1385,7 +1452,14 @@ CopyTo(CopyState cstate) CopySendChar(cstate, cstate->delim[0]); hdr_delim = true; - colname = NameStr(attr[attnum - 1]->attname); + if (SystemAttributeIsWritable(attnum)) + { + Form_pg_attribute attForm + = SystemAttributeDefinition(attnum, true); + colname = NameStr(attForm->attname); + } + else + colname = NameStr(attr[attnum - 1]->attname); CopyAttributeOutCSV(cstate, colname, false, list_length(cstate->attnumlist) == 1); @@ -1411,11 +1485,15 @@ CopyTo(CopyState cstate) { CHECK_FOR_INTERRUPTS(); + /* check Row-level permission on the tuple */ + if (!rowlvCopyToTuple(cstate->rel, tuple)) + continue; + /* Deconstruct the tuple ... faster than repeated heap_getattr */ heap_deform_tuple(tuple, tupDesc, values, nulls); /* Format and send the data */ - CopyOneRowTo(cstate, HeapTupleGetOid(tuple), values, nulls); + CopyOneRowTo(cstate, tuple, values, nulls); } heap_endscan(scandesc); @@ -1441,7 +1519,8 @@ CopyTo(CopyState cstate) * Emit one row during CopyTo(). */ static void -CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls) +CopyOneRowTo(CopyState cstate, HeapTuple tuple, + Datum *values, bool *nulls) { bool need_delim = false; FmgrInfo *out_functions = cstate->out_functions; @@ -1461,7 +1540,7 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls) { /* Hack --- assume Oid is same size as int32 */ CopySendInt32(cstate, sizeof(int32)); - CopySendInt32(cstate, tupleOid); + CopySendInt32(cstate, HeapTupleGetOid(tuple)); } } else @@ -1471,7 +1550,7 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls) if (cstate->oids) { string = DatumGetCString(DirectFunctionCall1(oidout, - ObjectIdGetDatum(tupleOid))); + ObjectIdGetDatum(HeapTupleGetOid(tuple)))); CopySendString(cstate, string); need_delim = true; } @@ -1480,8 +1559,11 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls) foreach(cur, cstate->attnumlist) { int attnum = lfirst_int(cur); - Datum value = values[attnum - 1]; - bool isnull = nulls[attnum - 1]; + Oid relid; + Datum value; + bool isnull; + bool force_quot; + FmgrInfo *out_fmgr; if (!cstate->binary) { @@ -1490,6 +1572,24 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls) need_delim = true; } + switch (attnum) + { + case SecurityAttributeNumber: + relid = RelationGetRelid(cstate->rel); + value = securitySysattSecLabelOut(relid, tuple); + isnull = false; + force_quot = cstate->seclabel_force_quot; + out_fmgr = &cstate->seclabel_out_function; + break; + + default: + value = values[attnum - 1]; + isnull = nulls[attnum - 1]; + force_quot = cstate->force_quote_flags[attnum - 1]; + out_fmgr = &out_functions[attnum - 1]; + break; + } + if (isnull) { if (!cstate->binary) @@ -1501,11 +1601,9 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls) { if (!cstate->binary) { - string = OutputFunctionCall(&out_functions[attnum - 1], - value); + string = OutputFunctionCall(out_fmgr, value); if (cstate->csv_mode) - CopyAttributeOutCSV(cstate, string, - cstate->force_quote_flags[attnum - 1], + CopyAttributeOutCSV(cstate, string, force_quot, list_length(cstate->attnumlist) == 1); else CopyAttributeOutText(cstate, string); @@ -1514,8 +1612,7 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls) { bytea *outputbytes; - outputbytes = SendFunctionCall(&out_functions[attnum - 1], - value); + outputbytes = SendFunctionCall(out_fmgr, value); CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ); CopySendData(cstate, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ); @@ -1649,8 +1746,10 @@ CopyFrom(CopyState cstate) num_defaults; FmgrInfo *in_functions; FmgrInfo oid_in_function; + FmgrInfo seclabel_in_function; Oid *typioparams; Oid oid_typioparam; + Oid seclabel_typioparam; int attnum; int i; Oid in_func_oid; @@ -1888,6 +1987,18 @@ CopyFrom(CopyState cstate) fmgr_info(in_func_oid, &oid_in_function); } + if (list_member_int(cstate->attnumlist, + SecurityAttributeNumber)) + { + if (!cstate->binary) + getTypeInputInfo(TEXTOID, + &in_func_oid, &seclabel_typioparam); + else + getTypeBinaryInputInfo(TEXTOID, + &in_func_oid, &seclabel_typioparam); + fmgr_info(in_func_oid, &seclabel_in_function); + } + values = (Datum *) palloc(num_phys_attrs * sizeof(Datum)); nulls = (bool *) palloc(num_phys_attrs * sizeof(bool)); @@ -1922,6 +2033,7 @@ CopyFrom(CopyState cstate) { bool skip_tuple; Oid loaded_oid = InvalidOid; + Oid loaded_seclabel = InvalidOid; CHECK_FOR_INTERRUPTS(); @@ -1993,14 +2105,21 @@ CopyFrom(CopyState cstate) /* Loop to read the user attributes on the line. */ foreach(cur, cstate->attnumlist) { + Form_pg_attribute attForm; + Datum dat; int attnum = lfirst_int(cur); int m = attnum - 1; + if (SystemAttributeIsWritable(attnum)) + attForm = SystemAttributeDefinition(attnum, true); + else + attForm = attr[m]; + if (fieldno >= fldct) ereport(ERROR, (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), errmsg("missing data for column \"%s\"", - NameStr(attr[m]->attname)))); + NameStr(attForm->attname)))); string = field_strings[fieldno++]; if (cstate->csv_mode && string == NULL && @@ -2010,14 +2129,40 @@ CopyFrom(CopyState cstate) string = cstate->null_print; } - cstate->cur_attname = NameStr(attr[m]->attname); + cstate->cur_attname = NameStr(attForm->attname); cstate->cur_attval = string; - values[m] = InputFunctionCall(&in_functions[m], - string, - typioparams[m], - attr[m]->atttypmod); - if (string != NULL) - nulls[m] = false; + + switch (attnum) + { + case SecurityAttributeNumber: + if (!string) + break; + + dat = InputFunctionCall(&seclabel_in_function, + string, + seclabel_typioparam, + attForm->atttypmod); + loaded_seclabel + = securityTransSecLabelIn(RelationGetRelid(cstate->rel), + TextDatumGetCString(dat)); + break; + + default: + if (cstate->csv_mode && string == NULL && + cstate->force_notnull_flags[m]) + { + /* Go ahead and read the NULL string */ + string = cstate->null_print; + } + + values[m] = InputFunctionCall(&in_functions[m], + string, + typioparams[m], + attForm->atttypmod); + if (string != NULL) + nulls[m] = false; + break; + } cstate->cur_attname = NULL; cstate->cur_attval = NULL; } @@ -2063,17 +2208,41 @@ CopyFrom(CopyState cstate) i = 0; foreach(cur, cstate->attnumlist) { + Form_pg_attribute attForm; + Datum dat; int attnum = lfirst_int(cur); int m = attnum - 1; - cstate->cur_attname = NameStr(attr[m]->attname); + if (SystemAttributeIsWritable(attnum)) + attForm = SystemAttributeDefinition(attnum, false); + else + attForm = attr[m]; + + cstate->cur_attname = NameStr(attForm->attname); i++; - values[m] = CopyReadBinaryAttribute(cstate, - i, - &in_functions[m], - typioparams[m], - attr[m]->atttypmod, - &nulls[m]); + + switch (attnum) + { + case SecurityAttributeNumber: + dat = CopyReadBinaryAttribute(cstate, i, + &seclabel_in_function, + seclabel_typioparam, + attForm->atttypmod, + &isnull); + if (!isnull) + loaded_seclabel + = securityTransSecLabelIn(RelationGetRelid(cstate->rel), + TextDatumGetCString(dat)); + break; + + default: + values[m] = CopyReadBinaryAttribute(cstate, i, + &in_functions[m], + typioparams[m], + attr[m]->atttypmod, + &nulls[m]); + break; + } cstate->cur_attname = NULL; } } @@ -2094,6 +2263,8 @@ CopyFrom(CopyState cstate) if (cstate->oids && file_has_oids) HeapTupleSetOid(tuple, loaded_oid); + if (HeapTupleHasSecid(tuple)) + HeapTupleSetSecid(tuple, loaded_seclabel); /* Triggers and stuff need to be invoked in query context. */ MemoryContextSwitchTo(oldcontext); @@ -2118,6 +2289,9 @@ CopyFrom(CopyState cstate) } if (!skip_tuple) + sepgsqlHeapTupleInsert(cstate->rel, tuple, false); + + if (!skip_tuple) { /* Place tuple in tuple slot */ ExecStoreTuple(tuple, slot, InvalidBuffer, false); @@ -3398,6 +3572,13 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) } if (attnum == InvalidAttrNumber) { + Form_pg_attribute attForm + = SystemAttributeByName(name, tupDesc->tdhasoid); + if (attForm && SystemAttributeIsWritable(attForm->attnum)) + attnum = attForm->attnum; + } + if (attnum == InvalidAttrNumber) + { if (rel != NULL) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -3445,7 +3626,8 @@ copy_dest_receive(TupleTableSlot *slot, DestReceiver *self) slot_getallattrs(slot); /* And send the data */ - CopyOneRowTo(cstate, InvalidOid, slot->tts_values, slot->tts_isnull); + CopyOneRowTo(cstate, slot->tts_tuple, + slot->tts_values, slot->tts_isnull); } /* diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index ec1db5a..ea35f05 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -33,6 +33,7 @@ #include "catalog/indexing.h" #include "catalog/pg_authid.h" #include "catalog/pg_database.h" +#include "catalog/pg_security.h" #include "catalog/pg_tablespace.h" #include "commands/comment.h" #include "commands/dbcommands.h" @@ -41,6 +42,7 @@ #include "miscadmin.h" #include "pgstat.h" #include "postmaster/bgwriter.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/fd.h" #include "storage/lmgr.h" @@ -111,6 +113,7 @@ createdb(const CreatedbStmt *stmt) bool new_record_nulls[Natts_pg_database]; Oid dboid; Oid datdba; + Oid datsecid; ListCell *option; DefElem *dtablespacename = NULL; DefElem *downer = NULL; @@ -119,6 +122,7 @@ createdb(const CreatedbStmt *stmt) DefElem *dcollate = NULL; DefElem *dctype = NULL; DefElem *dconnlimit = NULL; + DefElem *dseclabel = NULL; char *dbname = stmt->dbname; char *dbowner = NULL; const char *dbtemplate = NULL; @@ -200,6 +204,14 @@ createdb(const CreatedbStmt *stmt) errmsg("LOCATION is not supported anymore"), errhint("Consider using tablespaces instead."))); } + else if (strcmp(defel->defname, "security_context") == 0) + { + if (dseclabel) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dseclabel = defel; + } else elog(ERROR, "option \"%s\" not recognized", defel->defname); @@ -294,6 +306,9 @@ createdb(const CreatedbStmt *stmt) errmsg("template database \"%s\" does not exist", dbtemplate))); + /* SELinux checks db_database:{create} */ + datsecid = sepgsql_database_create(dbname, src_dboid, dseclabel); + /* * Permission check: to copy a DB that's not marked datistemplate, you * must be superuser or the owner thereof. @@ -557,6 +572,8 @@ createdb(const CreatedbStmt *stmt) new_record, new_record_nulls); HeapTupleSetOid(tuple, dboid); + if (HeapTupleHasSecid(tuple)) + HeapTupleSetSecid(tuple, datsecid); simple_heap_insert(pg_database_rel, tuple); @@ -573,6 +590,9 @@ createdb(const CreatedbStmt *stmt) /* Create pg_shdepend entries for objects within database */ copyTemplateDependencies(src_dboid, dboid); + /* Create pg_security entries for objects within database */ + securityOnCreateDatabase(src_dboid, dboid); + /* * Force a checkpoint before starting the copy. This will force dirty * buffers out to disk, to ensure source database is up-to-date on disk @@ -776,6 +796,9 @@ dropdb(const char *dbname, bool missing_ok) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, dbname); + /* SELinux checks db_database:{drop} permission */ + sepgsql_database_drop(db_id); + /* * Disallow dropping a DB that is marked istemplate. This is just to * prevent people from accidentally dropping template0 or template1; they @@ -829,6 +852,11 @@ dropdb(const char *dbname, bool missing_ok) dropDatabaseDependencies(db_id); /* + * Remove pg_security entries for the database. + */ + securityOnDropDatabase(db_id); + + /* * Drop pages for this database that are in the shared buffer cache. This * is important to ensure that no remaining backend tries to write out a * dirty buffer to the dead database later... @@ -913,6 +941,9 @@ RenameDatabase(const char *oldname, const char *newname) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to rename database"))); + /* SELinux: check db_database:{setattr} */ + sepgsql_database_alter(db_id); + /* * Make sure the new name doesn't exist. See notes for same error in * CREATE DATABASE. @@ -1025,6 +1056,9 @@ movedb(const char *dbname, const char *tblspcname) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, dbname); + /* SELinux checks db_database:{setattr} */ + sepgsql_database_alter(db_id); + /* * Obviously can't move the tables of my own database */ @@ -1377,6 +1411,9 @@ AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, stmt->dbname); + /* SELinux checks db_database:{setattr} */ + sepgsql_database_alter(HeapTupleGetOid(tuple)); + /* * Build an updated tuple, perusing the information just obtained */ @@ -1449,6 +1486,9 @@ AlterDatabaseSet(AlterDatabaseSetStmt *stmt) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, stmt->dbname); + /* SELinux checks db_database:{setattr} */ + sepgsql_database_alter(HeapTupleGetOid(tuple)); + memset(repl_repl, false, sizeof(repl_repl)); repl_repl[Anum_pg_database_datconfig - 1] = true; @@ -1571,6 +1611,9 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to change owner of database"))); + /* SELinux checks db_database:{setattr} */ + sepgsql_database_alter(HeapTupleGetOid(tuple)); + memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); @@ -1615,6 +1658,58 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId) */ } +/* + * ALTER DATABASE name SECURITY_LABEL [=] newlabel + */ +void +AlterDatabaseSecLabel(const char *dbname, DefElem *seclabel) +{ + Relation rel; + HeapTuple oldtup; + HeapTuple newtup; + ScanKeyData scankey; + SysScanDesc scan; + Oid secid; + bool replaces[Natts_pg_database]; + + /* Fetch the old tuple */ + rel = heap_open(DatabaseRelationId, RowExclusiveLock); + ScanKeyInit(&scankey, + Anum_pg_database_datname, + BTEqualStrategyNumber, F_NAMEEQ, + NameGetDatum(dbname)); + scan = systable_beginscan(rel, DatabaseNameIndexId, true, + SnapshotNow, 1, &scankey); + oldtup = systable_getnext(scan); + if (!HeapTupleIsValid(oldtup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", dbname))); + + memset(replaces, false, sizeof(replaces)); + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + NULL, NULL, replaces); + if (!HeapTupleHasSecid(newtup)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unable to set security label on \"%s\"", dbname))); + systable_endscan(scan); + + /* check DAC permission */ + if (!pg_database_ownercheck(HeapTupleGetOid(newtup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, dbname); + + /* SELinux checks db_database:{setattr relabelfrom relabelto} */ + secid = sepgsql_database_relabel(HeapTupleGetOid(newtup), seclabel); + HeapTupleSetSecid(newtup, secid); + + simple_heap_update(rel, &newtup->t_self, newtup); + CatalogUpdateIndexes(rel, newtup); + + heap_freetuple(newtup); + + heap_close(rel, RowExclusiveLock); +} /* * Helper functions diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index 46493b1..7236279 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -27,6 +27,7 @@ #include "foreign/foreign.h" #include "miscadmin.h" #include "parser/parse_func.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -233,6 +234,9 @@ AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId) if (form->fdwowner != newOwnerId) { + /* SELinux permission check */ + sepgsql_fdw_alter(fdwId, InvalidOid); + form->fdwowner = newOwnerId; simple_heap_update(rel, &tup->t_self, tup); @@ -297,6 +301,8 @@ AlterForeignServerOwner(const char *name, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname); } } + /* SELinux permission checks */ + sepgsql_foreign_server_alter(srvId); form->srvowner = newOwnerId; @@ -342,6 +348,7 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt) Oid fdwvalidator; Datum fdwoptions; Oid ownerId; + Oid secid; /* Must be super user */ if (!superuser()) @@ -380,6 +387,9 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt) else fdwvalidator = InvalidOid; + /* SELinux permission checks */ + secid = sepgsql_fdw_create(stmt->fdwname, fdwvalidator); + values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = fdwvalidator; nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true; @@ -393,6 +403,8 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt) nulls[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true; tuple = heap_form_tuple(rel->rd_att, values, nulls); + if (HeapTupleHasSecid(tuple)) + HeapTupleSetSecid(tuple, secid); fdwId = simple_heap_insert(rel, tuple); CatalogUpdateIndexes(rel, tuple); @@ -487,6 +499,9 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt) fdwvalidator = DatumGetObjectId(datum); } + /* SELinux permission checks */ + sepgsql_fdw_alter(fdwId, fdwvalidator); + /* * Options specified, validate and update. */ @@ -609,6 +624,7 @@ CreateForeignServer(CreateForeignServerStmt *stmt) HeapTuple tuple; Oid srvId; Oid ownerId; + Oid secid; AclResult aclresult; ObjectAddress myself; ObjectAddress referenced; @@ -636,6 +652,8 @@ CreateForeignServer(CreateForeignServerStmt *stmt) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname); + secid = sepgsql_foreign_server_create(stmt->fdwname); + /* * Insert tuple into pg_foreign_server. */ @@ -676,6 +694,8 @@ CreateForeignServer(CreateForeignServerStmt *stmt) nulls[Anum_pg_foreign_server_srvoptions - 1] = true; tuple = heap_form_tuple(rel->rd_att, values, nulls); + if (HeapTupleHasSecid(tuple)) + HeapTupleSetSecid(tuple, secid); srvId = simple_heap_insert(rel, tuple); @@ -732,6 +752,9 @@ AlterForeignServer(AlterForeignServerStmt *stmt) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, stmt->servername); + /* SELinux permission checks */ + sepgsql_foreign_server_alter(srvId); + memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index f0989bf..0668bd7 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -53,6 +53,7 @@ #include "parser/parse_expr.h" #include "parser/parse_func.h" #include "parser/parse_type.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -517,7 +518,8 @@ compute_attributes_sql_style(List *options, bool *security_definer, ArrayType **proconfig, float4 *procost, - float4 *prorows) + float4 *prorows, + Node **proseclabel) { ListCell *option; DefElem *as_item = NULL; @@ -529,6 +531,7 @@ compute_attributes_sql_style(List *options, List *set_items = NIL; DefElem *cost_item = NULL; DefElem *rows_item = NULL; + DefElem *seclabel_item = NULL; foreach(option, options) { @@ -558,6 +561,14 @@ compute_attributes_sql_style(List *options, errmsg("conflicting or redundant options"))); windowfunc_item = defel; } + else if (strcmp(defel->defname, "security_context") == 0) + { + if (seclabel_item) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + seclabel_item = defel; + } else if (compute_common_attribute(defel, &volatility_item, &strict_item, @@ -622,6 +633,8 @@ compute_attributes_sql_style(List *options, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("ROWS must be positive"))); } + if (seclabel_item) + *proseclabel = (Node *)seclabel_item; } @@ -762,6 +775,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) ArrayType *proconfig; float4 procost; float4 prorows; + Node *proseclabel; HeapTuple languageTuple; Form_pg_language languageStruct; List *as_clause; @@ -784,13 +798,14 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) proconfig = NULL; procost = -1; /* indicates not set */ prorows = -1; /* indicates not set */ + proseclabel = NULL; /* override attributes from explicit list */ compute_attributes_sql_style(stmt->options, &as_clause, &language, &isWindowFunc, &volatility, &isStrict, &security, - &proconfig, &procost, &prorows); + &proconfig, &procost, &prorows, &proseclabel); /* Convert language name to canonical case */ languageName = case_translate_language_name(language); @@ -926,7 +941,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) parameterDefaults, PointerGetDatum(proconfig), procost, - prorows); + prorows, + proseclabel); } @@ -1112,6 +1128,9 @@ RenameFunction(List *name, List *argtypes, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* SELinux permission checks */ + sepgsql_proc_alter(procOid, newname, InvalidOid); + /* rename */ namestrcpy(&(procForm->proname), newname); simple_heap_update(rel, &tup->t_self, tup); @@ -1220,6 +1239,8 @@ AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(procForm->pronamespace)); } + /* SELinux permission checks */ + sepgsql_proc_alter(procOid, NULL, InvalidOid); memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); @@ -1258,6 +1279,59 @@ AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) } /* + * ALTER FUNCTION name(args,...) SECURITY_LABEL [=] newlabel + */ +void +AlterFunctionSecLabel(List *name, List *argtypes, DefElem *seclabel) +{ + Relation rel; + HeapTuple oldtup; + HeapTuple newtup; + Oid procOid; + Oid secid; + bool replaces[Natts_pg_proc]; + + /* open pg_proc system catalog */ + rel = heap_open(ProcedureRelationId, RowExclusiveLock); + + procOid = LookupFuncNameTypeNames(name, argtypes, false); + + oldtup = SearchSysCache(PROCOID, + ObjectIdGetDatum(procOid), + 0, 0, 0); + if (!HeapTupleIsValid(oldtup)) + elog(ERROR, "cache lookup failed for function %u", procOid); + + memset(replaces, false, sizeof(replaces)); + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + NULL, NULL, replaces); + + if (!HeapTupleHasSecid(newtup)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unable to set security label on \"%s\"", + get_func_name(procOid)))); + + ReleaseSysCache(oldtup); + + /* DAC permission checks */ + if (!pg_proc_ownercheck(HeapTupleGetOid(newtup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, + get_func_name(HeapTupleGetOid(newtup))); + + /* SELinux permission checks */ + secid = sepgsql_proc_relabel(procOid, seclabel); + HeapTupleSetSecid(newtup, secid); + + simple_heap_update(rel, &newtup->t_self, newtup); + CatalogUpdateIndexes(rel, newtup); + + heap_freetuple(newtup); + + heap_close(rel, RowExclusiveLock); +} + +/* * Implements the ALTER FUNCTION utility command (except for the * RENAME and OWNER clauses, which are handled as part of the generic * ALTER framework). @@ -1296,6 +1370,9 @@ AlterFunction(AlterFunctionStmt *stmt) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->func->funcname)); + /* SELinux checks permissions */ + sepgsql_proc_alter(funcOid, NULL, InvalidOid); + if (procForm->proisagg) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -1473,6 +1550,7 @@ CreateCast(CreateCastStmt *stmt) char sourcetyptype; char targettyptype; Oid funcid; + Oid secid; int nargs; char castcontext; char castmethod; @@ -1674,6 +1752,8 @@ CreateCast(CreateCastStmt *stmt) castcontext = 0; /* keep compiler quiet */ break; } + /* SELinux permission check */ + secid = sepgsql_cast_create(sourcetypeid, targettypeid, funcid); relation = heap_open(CastRelationId, RowExclusiveLock); @@ -1704,6 +1784,9 @@ CreateCast(CreateCastStmt *stmt) tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls); + if (HeapTupleHasSecid(tuple)) + HeapTupleSetSecid(tuple, secid); + simple_heap_insert(relation, tuple); CatalogUpdateIndexes(relation, tuple); @@ -1897,6 +1980,9 @@ AlterFunctionNamespace(List *name, List *argtypes, bool isagg, NameStr(proc->proname), newschema))); + /* SELinux checks permissions */ + sepgsql_proc_alter(procOid, NULL, nspOid); + /* OK, modify the pg_proc row */ /* tup is a copy, so we can scribble directly on it */ diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 99ab0e5..78b4455 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -37,6 +37,7 @@ #include "parser/parse_coerce.h" #include "parser/parse_func.h" #include "parser/parsetree.h" +#include "security/sepgsql.h" #include "storage/lmgr.h" #include "storage/proc.h" #include "storage/procarray.h" @@ -197,6 +198,9 @@ DefineIndex(RangeVar *heapRelation, if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceId)); + + /* SELinux checks */ + sepgsql_index_create(relationId, namespaceId); } /* diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 1e5c92e..6949b54 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -20,6 +20,7 @@ #include "commands/lockcmds.h" #include "miscadmin.h" #include "parser/parse_clause.h" +#include "security/sepgsql.h" #include "storage/lmgr.h" #include "utils/acl.h" #include "utils/lsyscache.h" @@ -140,6 +141,9 @@ LockTableRecurse(Oid reloid, RangeVar *rv, errmsg("\"%s\" is not a table", RelationGetRelationName(rel)))); + /* SELinux: check db_table:{lock} permission */ + sepgsql_relation_lock(reloid); + /* * If requested, recurse to children. We use find_inheritance_children * not find_all_inheritors to avoid taking locks far in advance of diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index 84dc2ce..b23919d 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -35,6 +35,7 @@ #include "parser/parse_func.h" #include "parser/parse_oper.h" #include "parser/parse_type.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -177,6 +178,7 @@ CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid) HeapTuple tup; Datum values[Natts_pg_opfamily]; bool nulls[Natts_pg_opfamily]; + Oid opfSecid; NameData opfName; ObjectAddress myself, referenced; @@ -197,6 +199,9 @@ CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid) errmsg("operator family \"%s\" for access method \"%s\" already exists", opfname, amname))); + /* SELinux check permission */ + opfSecid = sepgsql_opfamily_create(opfname, namespaceoid); + /* * Okay, let's create the pg_opfamily entry. */ @@ -210,6 +215,8 @@ CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid) values[Anum_pg_opfamily_opfowner - 1] = ObjectIdGetDatum(GetUserId()); tup = heap_form_tuple(rel->rd_att, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, opfSecid); opfamilyoid = simple_heap_insert(rel, tup); @@ -265,6 +272,7 @@ DefineOpClass(CreateOpClassStmt *stmt) Form_pg_am pg_am; Datum values[Natts_pg_opclass]; bool nulls[Natts_pg_opclass]; + Oid opcSecid; AclResult aclresult; NameData opcName; ObjectAddress myself, @@ -353,6 +361,9 @@ DefineOpClass(CreateOpClassStmt *stmt) NameListToString(stmt->opfamilyname), stmt->amname))); opfamilyoid = HeapTupleGetOid(tup); + /* SELinux checks permission */ + sepgsql_opfamily_alter(opfamilyoid, NULL); + /* * XXX given the superuser check above, there's no need for an * ownership check here @@ -371,6 +382,9 @@ DefineOpClass(CreateOpClassStmt *stmt) { opfamilyoid = HeapTupleGetOid(tup); + /* SELinux checks permission */ + sepgsql_opfamily_alter(opfamilyoid, NULL); + /* * XXX given the superuser check above, there's no need for an * ownership check here @@ -441,6 +455,8 @@ DefineOpClass(CreateOpClassStmt *stmt) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, get_func_name(funcOid)); #endif + /* SELinux check permission */ + sepgsql_opfamily_add_operator(opfamilyoid, operOid); /* Save the info */ member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); @@ -465,6 +481,8 @@ DefineOpClass(CreateOpClassStmt *stmt) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, get_func_name(funcOid)); #endif + /* SELinux check permission */ + sepgsql_opfamily_add_procedure(opfamilyoid, funcOid); /* Save the info */ member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); @@ -531,6 +549,9 @@ DefineOpClass(CreateOpClassStmt *stmt) errmsg("operator class \"%s\" for access method \"%s\" already exists", opcname, stmt->amname))); + /* SELinux permission check */ + opcSecid = sepgsql_opclass_create(opcname, namespaceoid); + /* * If we are creating a default opclass, check there isn't one already. * (Note we do not restrict this test to visible opclasses; this ensures @@ -657,6 +678,7 @@ DefineOpFamily(CreateOpFamilyStmt *stmt) HeapTuple tup; Datum values[Natts_pg_opfamily]; bool nulls[Natts_pg_opfamily]; + Oid opfSecid; AclResult aclresult; NameData opfName; ObjectAddress myself, @@ -699,6 +721,9 @@ DefineOpFamily(CreateOpFamilyStmt *stmt) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create an operator family"))); + /* SELinux permission check */ + opfSecid = sepgsql_opfamily_create(opfname, namespaceoid); + rel = heap_open(OperatorFamilyRelationId, RowExclusiveLock); /* @@ -773,6 +798,7 @@ AlterOpFamily(AlterOpFamilyStmt *stmt) int maxOpNumber, /* amstrategies value */ maxProcNumber; /* amsupport value */ HeapTuple tup; + Oid opfSecid; Form_pg_am pg_am; /* Get necessary info about access method */ @@ -805,6 +831,7 @@ AlterOpFamily(AlterOpFamilyStmt *stmt) errmsg("operator family \"%s\" does not exist for access method \"%s\"", NameListToString(stmt->opfamilyname), stmt->amname))); opfamilyoid = HeapTupleGetOid(tup); + opfSecid = HeapTupleGetSecid(tup); ReleaseSysCache(tup); /* @@ -817,6 +844,9 @@ AlterOpFamily(AlterOpFamilyStmt *stmt) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to alter an operator family"))); + /* SELinux permission checks */ + sepgsql_opfamily_alter(opfamilyoid, NULL); + /* * ADD and DROP cases need separate code from here on down. */ @@ -893,6 +923,8 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid, aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, get_func_name(funcOid)); #endif + /* SELinux permission check */ + sepgsql_opfamily_add_operator(opfamilyoid, operOid); /* Save the info */ member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); @@ -917,6 +949,8 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid, aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, get_func_name(funcOid)); #endif + /* SELinux permission check */ + sepgsql_opfamily_add_procedure(opfamilyoid, funcOid); /* Save the info */ member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); @@ -1815,6 +1849,9 @@ RenameOpClass(List *name, const char *access_method, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* SELinux permission checks */ + sepgsql_opclass_alter(opcOid, newname); + /* rename */ namestrcpy(&(((Form_pg_opclass) GETSTRUCT(tup))->opcname), newname); simple_heap_update(rel, &tup->t_self, tup); @@ -1915,6 +1952,9 @@ RenameOpFamily(List *name, const char *access_method, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* SELinux check permissions */ + sepgsql_opfamily_alter(opfOid, newname); + /* rename */ namestrcpy(&(((Form_pg_opfamily) GETSTRUCT(tup))->opfname), newname); simple_heap_update(rel, &tup->t_self, tup); @@ -2035,6 +2075,8 @@ AlterOpClassOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); } + /* SELinux permission check */ + sepgsql_opclass_alter(HeapTupleGetOid(tup), NULL); /* * Modify the owner --- okay to scribble on tup because it's a copy @@ -2162,6 +2204,8 @@ AlterOpFamilyOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); } + /* SELinux permission checks */ + sepgsql_opfamily_alter(HeapTupleGetOid(tup), NULL); /* * Modify the owner --- okay to scribble on tup because it's a copy diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c index 6c05611..c934424 100644 --- a/src/backend/commands/operatorcmds.c +++ b/src/backend/commands/operatorcmds.c @@ -45,6 +45,7 @@ #include "parser/parse_func.h" #include "parser/parse_oper.h" #include "parser/parse_type.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -432,6 +433,8 @@ AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(oprForm->oprnamespace)); } + /* SELinux permission check */ + sepgsql_operator_alter(operOid); /* * Modify the owner --- okay to scribble on tup because it's a copy diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c index 3faf445..8f1e212 100644 --- a/src/backend/commands/proclang.c +++ b/src/backend/commands/proclang.c @@ -30,6 +30,7 @@ #include "miscadmin.h" #include "parser/gramparse.h" #include "parser/parse_func.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -151,7 +152,8 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) NIL, PointerGetDatum(NULL), 1, - 0); + 0, + NULL); } /* @@ -186,7 +188,8 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) NIL, PointerGetDatum(NULL), 1, - 0); + 0, + NULL); } } else @@ -275,10 +278,16 @@ create_proc_lang(const char *languageName, bool nulls[Natts_pg_language]; NameData langname; HeapTuple tup; + Oid langSecid; ObjectAddress myself, referenced; /* + * SELinux permission checks + */ + langSecid = sepgsql_language_create(languageName, handlerOid, valOid); + + /* * Insert the new language into pg_language */ rel = heap_open(LanguageRelationId, RowExclusiveLock); @@ -297,6 +306,8 @@ create_proc_lang(const char *languageName, nulls[Anum_pg_language_lanacl - 1] = true; tup = heap_form_tuple(tupDesc, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, langSecid); simple_heap_insert(rel, tup); @@ -518,6 +529,9 @@ RenameLanguage(const char *oldname, const char *newname) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, oldname); + /* SELinux permission checks */ + sepgsql_language_alter(HeapTupleGetOid(tup)); + /* rename */ namestrcpy(&(((Form_pg_language) GETSTRUCT(tup))->lanname), newname); simple_heap_update(rel, &tup->t_self, tup); @@ -613,6 +627,9 @@ AlterLanguageOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) /* Must be able to become new owner */ check_is_member_of_role(GetUserId(), newOwnerId); + /* SELinux permission checks */ + sepgsql_language_alter(HeapTupleGetOid(tup)); + memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c index 0d047cf..748bdd6 100644 --- a/src/backend/commands/schemacmds.c +++ b/src/backend/commands/schemacmds.c @@ -25,6 +25,7 @@ #include "commands/schemacmds.h" #include "miscadmin.h" #include "parser/parse_utilcmd.h" +#include "security/sepgsql.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" @@ -48,6 +49,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) ListCell *parsetree_item; Oid owner_uid; Oid saved_uid; + Oid nspsecid; int save_sec_context; AclResult aclresult; @@ -75,6 +77,10 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) check_is_member_of_role(saved_uid, owner_uid); + /* SELinux checks db_schema:{create} */ + nspsecid = sepgsql_schema_create(schemaName, false, + (DefElem *)stmt->secLabel); + /* Additional check to protect reserved schema names */ if (!allowSystemTableMods && IsReservedName(schemaName)) ereport(ERROR, @@ -95,7 +101,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) save_sec_context | SECURITY_LOCAL_USERID_CHANGE); /* Create the schema's namespace */ - namespaceId = NamespaceCreate(schemaName, owner_uid); + namespaceId = NamespaceCreate(schemaName, owner_uid, nspsecid); /* Advance cmd counter to make the namespace visible */ CommandCounterIncrement(); @@ -268,8 +274,7 @@ RenameSchema(const char *oldname, const char *newname) errmsg("schema \"%s\" does not exist", oldname))); /* make sure the new name doesn't exist */ - if (HeapTupleIsValid( - SearchSysCache(NAMESPACENAME, + if (HeapTupleIsValid(SearchSysCache(NAMESPACENAME, CStringGetDatum(newname), 0, 0, 0))) ereport(ERROR, @@ -287,6 +292,9 @@ RenameSchema(const char *oldname, const char *newname) aclcheck_error(aclresult, ACL_KIND_DATABASE, get_database_name(MyDatabaseId)); + /* SELinux checks db_schema:{setattr} */ + sepgsql_schema_alter(HeapTupleGetOid(tup)); + if (!allowSystemTableMods && IsReservedName(newname)) ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), @@ -398,6 +406,9 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_DATABASE, get_database_name(MyDatabaseId)); + /* SELinux checks db_schema:{setattr} */ + sepgsql_schema_alter(HeapTupleGetOid(tup)); + memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); @@ -432,3 +443,51 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) } } + +/* + * ALTER SCHEMA name SECURITY_LABEL [=] newlabel + */ +void +AlterSchemaSecLabel(const char *name, DefElem *secLabel) +{ + Relation rel; + HeapTuple oldtup; + HeapTuple newtup; + Oid secid; + bool replaces[Natts_pg_namespace]; + + /* open pg_namespace relation */ + rel = heap_open(NamespaceRelationId, RowExclusiveLock); + oldtup = SearchSysCache(NAMESPACENAME, + CStringGetDatum(name), + 0, 0, 0); + if (!HeapTupleIsValid(oldtup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("schema \"%s\" does not exist", name))); + + memset(replaces, false, sizeof(replaces)); + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + NULL, NULL, replaces); + if (!HeapTupleHasSecid(newtup)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unable to set security label on \"%s\"", name))); + + ReleaseSysCache(oldtup); + + /* DAC permission check */ + if (!pg_namespace_ownercheck(HeapTupleGetOid(newtup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, name); + /* SELinux checks db_schema:{setattr relabelfrom relabelto} */ + secid = sepgsql_schema_relabel(HeapTupleGetOid(newtup), secLabel); + HeapTupleSetSecid(newtup, secid); + + simple_heap_update(rel, &newtup->t_self, newtup); + + CatalogUpdateIndexes(rel, newtup); + + heap_freetuple(newtup); + + heap_close(rel, RowExclusiveLock); +} diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index abc65aa..9507ef1 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -26,6 +26,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/proc.h" @@ -201,6 +202,7 @@ DefineSequence(CreateSeqStmt *seq) stmt->options = list_make1(defWithOids(false)); stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; + stmt->secLabel = seq->secLabel; seqoid = DefineRelation(stmt, RELKIND_SEQUENCE); @@ -328,6 +330,8 @@ AlterSequence(AlterSeqStmt *stmt) if (!pg_class_ownercheck(relid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, stmt->sequence->relname); + /* SELinux checks db_sequence:{setattr} */ + sepgsql_relation_alter(relid, NULL, InvalidOid); /* do the work */ AlterSequenceInternal(relid, stmt->options); @@ -467,6 +471,9 @@ nextval_internal(Oid relid) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); + /* SELinux check db_sequence:{next_value} */ + sepgsql_sequence_next_value(elm->relid); + if (elm->last != elm->cached) /* some numbers were cached */ { Assert(elm->last_valid); @@ -662,6 +669,9 @@ currval_oid(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); + /* SELinux check db_sequence:{get_value} */ + sepgsql_sequence_get_value(elm->relid); + if (!elm->last_valid) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -706,6 +716,9 @@ lastval(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); + /* SELinux check db_sequence:{get_value} */ + sepgsql_sequence_get_value(last_used_seq->relid); + result = last_used_seq->last; relation_close(seqrel, NoLock); @@ -742,6 +755,9 @@ do_setval(Oid relid, int64 next, bool iscalled) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); + /* SELinux check db_sequence:{set_value} */ + sepgsql_sequence_set_value(elm->relid); + /* lock page' buffer and read tuple */ seq = read_info(elm, seqrel, &buf); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 96dda00..85b8800 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -62,6 +62,7 @@ #include "parser/parser.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteHandler.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/smgr.h" @@ -260,8 +261,8 @@ static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, static void ATRewriteTables(List **wqueue); static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap); static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel); -static void ATSimplePermissions(Relation rel, bool allowView); -static void ATSimplePermissionsRelationOrIndex(Relation rel); +static void ATSimplePermissions(Relation rel, const char *colname, bool allowView); +static void ATSimplePermissionsRelationOrIndex(Relation rel, const char *colname); static void ATSimpleRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, bool recurse); static void ATOneLevelRecursion(List **wqueue, Relation rel, @@ -351,6 +352,7 @@ DefineRelation(CreateStmt *stmt, char relkind) List *rawDefaults; List *cookedDefaults; Datum reloptions; + Oid *secLabels; ListCell *listptr; AttrNumber attnum; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; @@ -454,6 +456,16 @@ DefineRelation(CreateStmt *stmt, char relkind) localHasOids = interpretOidsOption(stmt->options); descriptor->tdhasoid = (localHasOids || parentOidCount > 0); + /* SELinux checks db_table:{create} and db_column:{create} */ + secLabels = sepgsql_relation_create(relname, + relkind, + descriptor, + namespaceId, + (DefElem *)stmt->secLabel, + schema, + false, + true); + /* * Find columns with default values and prepare for insertion of the * defaults. Pre-cooked (that is, inherited) defaults go into a list of @@ -523,7 +535,8 @@ DefineRelation(CreateStmt *stmt, char relkind) parentOidCount, stmt->oncommit, reloptions, - allowSystemTableMods); + allowSystemTableMods, + secLabels); StoreCatalogInheritance(relationId, inheritOids); @@ -897,6 +910,8 @@ ExecuteTruncate(TruncateStmt *stmt) if (!pg_class_ownercheck(seq_relid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(seq_rel)); + /* SELinux checks */ + sepgsql_relation_alter(seq_relid, NULL, InvalidOid); seq_relids = lappend_oid(seq_relids, seq_relid); @@ -1052,6 +1067,9 @@ truncate_check_rel(Relation rel) errmsg("permission denied: \"%s\" is a system catalog", RelationGetRelationName(rel)))); + /* SELinux: check db_table:{delete} permission */ + sepgsql_relation_truncate(rel); + /* * We can never allow truncation of shared or nailed-in-cache relations, * because we can't support changing their relfilenode values. @@ -1226,6 +1244,8 @@ MergeAttributes(List *schema, List *supers, bool istemp, if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); + /* SELinux checks db_table:{setattr} */ + sepgsql_relation_alter(RelationGetRelid(relation), NULL, InvalidOid); /* * Reject duplications in the list of parents. @@ -1931,6 +1951,9 @@ renameatt(Oid myrelid, errmsg("cannot rename system column \"%s\"", oldattname))); + /* SELinux checks db_column:{setattr} */ + sepgsql_attribute_alter(myrelid, oldattname); + /* * if the attribute is inherited, forbid the renaming, unless we are * already inside a recursive rename. @@ -2036,6 +2059,9 @@ RenameRelation(Oid myrelid, const char *newrelname, ObjectType reltype) Oid namespaceId; char relkind; + /* SELinux checks */ + sepgsql_relation_alter(myrelid, newrelname, InvalidOid); + /* * Grab an exclusive lock on the target table, index, sequence or view, * which we will NOT release until end of transaction. @@ -2369,14 +2395,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, switch (cmd->subtype) { case AT_AddColumn: /* ADD COLUMN */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* Performs own recursion */ ATPrepAddColumn(wqueue, rel, recurse, cmd); pass = AT_PASS_ADD_COL; break; case AT_AddColumnToView: /* add column via CREATE OR REPLACE * VIEW */ - ATSimplePermissions(rel, true); + ATSimplePermissions(rel, NULL, true); /* Performs own recursion */ ATPrepAddColumn(wqueue, rel, recurse, cmd); pass = AT_PASS_ADD_COL; @@ -2389,19 +2415,19 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, * substitutes default values into INSERTs before it expands * rules. */ - ATSimplePermissions(rel, true); + ATSimplePermissions(rel, cmd->name, true); ATSimpleRecursion(wqueue, rel, cmd, recurse); /* No command-specific prep needed */ pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP; break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, cmd->name, false); ATSimpleRecursion(wqueue, rel, cmd, recurse); /* No command-specific prep needed */ pass = AT_PASS_DROP; break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, cmd->name, false); ATSimpleRecursion(wqueue, rel, cmd, recurse); /* No command-specific prep needed */ pass = AT_PASS_ADD_CONSTR; @@ -2413,13 +2439,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_COL_ATTRS; break; case AT_SetStorage: /* ALTER COLUMN STORAGE */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, cmd->name, false); ATSimpleRecursion(wqueue, rel, cmd, recurse); /* No command-specific prep needed */ pass = AT_PASS_COL_ATTRS; break; case AT_DropColumn: /* DROP COLUMN */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) @@ -2427,13 +2453,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_DROP; break; case AT_AddIndex: /* ADD INDEX */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* This command never recurses */ /* No command-specific prep needed */ pass = AT_PASS_ADD_INDEX; break; case AT_AddConstraint: /* ADD CONSTRAINT */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) @@ -2441,7 +2467,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_ADD_CONSTR; break; case AT_DropConstraint: /* DROP CONSTRAINT */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) @@ -2449,7 +2475,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_DROP; break; case AT_AlterColumnType: /* ALTER COLUMN TYPE */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, cmd->name, false); /* Performs own recursion */ ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd); pass = AT_PASS_ALTER_TYPE; @@ -2461,20 +2487,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_ClusterOn: /* CLUSTER ON */ case AT_DropCluster: /* SET WITHOUT CLUSTER */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* These commands never recurse */ /* No command-specific prep needed */ pass = AT_PASS_MISC; break; case AT_AddOids: /* SET WITH OIDS */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* Performs own recursion */ if (!rel->rd_rel->relhasoids || recursing) ATPrepAddOids(wqueue, rel, recurse, cmd); pass = AT_PASS_ADD_COL; break; case AT_DropOids: /* SET WITHOUT OIDS */ - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* Performs own recursion */ if (rel->rd_rel->relhasoids) { @@ -2488,14 +2514,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_DROP; break; case AT_SetTableSpace: /* SET TABLESPACE */ - ATSimplePermissionsRelationOrIndex(rel); + ATSimplePermissionsRelationOrIndex(rel, NULL); /* This command never recurses */ ATPrepSetTableSpace(tab, rel, cmd->name); pass = AT_PASS_MISC; /* doesn't actually matter */ break; case AT_SetRelOptions: /* SET (...) */ case AT_ResetRelOptions: /* RESET (...) */ - ATSimplePermissionsRelationOrIndex(rel); + ATSimplePermissionsRelationOrIndex(rel, NULL); /* This command never recurses */ /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -2514,7 +2540,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_DisableRule: case AT_AddInherit: /* INHERIT / NO INHERIT */ case AT_DropInherit: - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* These commands never recurse */ /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -2860,8 +2886,9 @@ ATRewriteTables(List **wqueue) /* * The new relation is local to our transaction and we know * nothing depends on it, so DROP_RESTRICT should be OK. + * SELinux does not apply any permission checks here. */ - performDeletion(&object, DROP_RESTRICT); + performDeletionNoPerms(&object, DROP_RESTRICT); /* performDeletion does CommandCounterIncrement at end */ /* @@ -3086,11 +3113,14 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) if (newrel) { Oid tupOid = InvalidOid; + Oid tupSecid = InvalidOid; /* Extract data from old tuple */ heap_deform_tuple(tuple, oldTupDesc, values, isnull); if (oldTupDesc->tdhasoid) tupOid = HeapTupleGetOid(tuple); + if (HeapTupleHasSecid(tuple)) + tupSecid = HeapTupleGetSecid(tuple); /* Set dropped attributes to null in new tuple */ foreach(lc, dropped_attrs) @@ -3122,6 +3152,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) /* Preserve OID, if any */ if (newTupDesc->tdhasoid) HeapTupleSetOid(tuple, tupOid); + /* Preserve SID, if any */ + if (HeapTupleHasSecid(tuple)) + HeapTupleSetSecid(tuple, tupSecid); } /* Now check any constraints on the possibly-changed tuple */ @@ -3223,7 +3256,7 @@ ATGetQueueEntry(List **wqueue, Relation rel) * - Ensure that it is not a system table */ static void -ATSimplePermissions(Relation rel, bool allowView) +ATSimplePermissions(Relation rel, const char *colName, bool allowView) { if (rel->rd_rel->relkind != RELKIND_RELATION) { @@ -3247,6 +3280,12 @@ ATSimplePermissions(Relation rel, bool allowView) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel)); + /* SELinux checks */ + if (!colName) + sepgsql_relation_alter(RelationGetRelid(rel), NULL, InvalidOid); + else + sepgsql_attribute_alter(RelationGetRelid(rel), colName); + if (!allowSystemTableMods && IsSystemRelation(rel)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -3262,7 +3301,7 @@ ATSimplePermissions(Relation rel, bool allowView) * - Ensure that it is not a system table */ static void -ATSimplePermissionsRelationOrIndex(Relation rel) +ATSimplePermissionsRelationOrIndex(Relation rel, const char *colName) { if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_INDEX) @@ -3276,6 +3315,12 @@ ATSimplePermissionsRelationOrIndex(Relation rel) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel)); + /* SELinux checks */ + if (!colName) + sepgsql_relation_alter(RelationGetRelid(rel), NULL, InvalidOid); + else + sepgsql_attribute_alter(RelationGetRelid(rel), colName); + if (!allowSystemTableMods && IsSystemRelation(rel)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -3519,6 +3564,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, HeapTuple typeTuple; Oid typeOid; int32 typmod; + Oid attsecid; Form_pg_type tform; Expr *defval; @@ -3556,6 +3602,9 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, errmsg("child table \"%s\" has a conflicting \"%s\" column", RelationGetRelationName(rel), colDef->colname))); + /* SELinux checks db_column:{setattr} */ + sepgsql_attribute_alter(myrelid, colDef->colname); + /* Bump the existing child att's inhcount */ childatt->attinhcount++; simple_heap_update(attrdesc, &tuple->t_self, tuple); @@ -3595,6 +3644,9 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, errmsg("column \"%s\" of relation \"%s\" already exists", colDef->colname, RelationGetRelationName(rel)))); + /* SELinux checks db_column:{create} */ + attsecid = sepgsql_attribute_create(myrelid, colDef); + /* Determine the new attribute's number */ if (isOid) newattnum = ObjectIdAttributeNumber; @@ -3637,7 +3689,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, ReleaseSysCache(typeTuple); - InsertPgAttributeTuple(attrdesc, &attribute, NULL); + InsertPgAttributeTuple(attrdesc, &attribute, NULL, attsecid); heap_close(attrdesc, RowExclusiveLock); @@ -4026,6 +4078,8 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *flagValue) if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel)); + /* SELinux checks */ + sepgsql_attribute_alter(RelationGetRelid(rel), colName); } static void @@ -4181,7 +4235,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* * get the number of the attribute @@ -4483,7 +4537,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); /* * Call AddRelationNewConstraints to do the work, making sure it works on @@ -5385,7 +5439,7 @@ ATExecDropConstraint(Relation rel, const char *constrName, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) - ATSimplePermissions(rel, false); + ATSimplePermissions(rel, NULL, false); conrel = heap_open(ConstraintRelationId, RowExclusiveLock); @@ -6319,6 +6373,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); } + /* SELinux checks db_table:{setattr} */ + sepgsql_relation_alter(relationOid, NULL, InvalidOid); } memset(repl_null, false, sizeof(repl_null)); @@ -6923,7 +6979,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent) * Must be owner of both parent and child -- child was checked by * ATSimplePermissions call in ATPrepCmd */ - ATSimplePermissions(parent_rel, false); + ATSimplePermissions(parent_rel, NULL, false); /* Permanent rels cannot inherit from temporary ones */ if (parent_rel->rd_istemp && !child_rel->rd_istemp) @@ -7581,6 +7637,9 @@ AlterTableNamespace(RangeVar *relation, const char *newschema, RelationGetRelationName(rel), newschema))); + /* SELinux checks */ + sepgsql_relation_alter(relid, NULL, nspOid); + /* disallow renaming into or out of temp schemas */ if (isAnyTempNamespace(nspOid) || isAnyTempNamespace(oldNspOid)) ereport(ERROR, @@ -7773,6 +7832,134 @@ AlterSeqNamespaces(Relation classRel, Relation rel, relation_close(depRel, AccessShareLock); } +/* + * ALTER TABLE/SEQUENCE name SECURITY_LABEL [=] newlabel + * ALTER TABLE/SEQUENCE name ALTER column SECURITY_LABEL [=] newlabel + */ +static void +ExecRelationSetSecLabel(Oid relid, DefElem *seclabel) +{ + Relation rel; + HeapTuple oldtup; + HeapTuple newtup; + Oid secid; + bool replaces[Natts_pg_class]; + + rel = heap_open(RelationRelationId, RowExclusiveLock); + oldtup = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(oldtup)) + elog(ERROR, "cache lookup failed for relation: %u", relid); + + memset(replaces, false, sizeof(replaces)); + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + NULL, NULL, replaces); + if (!HeapTupleHasSecid(newtup)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unable to set security label on \"%s\"", + get_rel_name(relid)))); + + ReleaseSysCache(oldtup); + + /* SELinux checks db_table:{setattr relabelfrom relabelto} */ + secid = sepgsql_relation_relabel(relid, seclabel); + + HeapTupleSetSecid(newtup, secid); + + simple_heap_update(rel, &newtup->t_self, newtup); + + CatalogUpdateIndexes(rel, newtup); + + heap_freetuple(newtup); + + heap_close(rel, RowExclusiveLock); +} + +static void +ExecAttributeSetSecLabel(Oid relid, const char *attname, DefElem *seclabel) +{ + Relation rel; + HeapTuple oldtup; + HeapTuple newtup; + AttrNumber attnum; + Oid secid; + bool replaces[Natts_pg_attribute]; + + rel = heap_open(AttributeRelationId, RowExclusiveLock); + oldtup = SearchSysCacheAttName(relid, attname); + if (!HeapTupleIsValid(oldtup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + attname, get_rel_name(relid)))); + attnum = ((Form_pg_attribute) GETSTRUCT(oldtup))->attnum; + + memset(replaces, false, sizeof(replaces)); + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + NULL, NULL, replaces); + if (!HeapTupleHasSecid(newtup)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unable to set security context on \"%s.%s\"", + get_rel_name(relid), attname))); + + ReleaseSysCache(oldtup); + + /* SELinux checks db_column:{setattr relabelfrom relabelto} */ + secid = sepgsql_attribute_relabel(relid, attnum, seclabel); + + HeapTupleSetSecid(newtup, secid); + + simple_heap_update(rel, &newtup->t_self, newtup); + + CatalogUpdateIndexes(rel, newtup); + + heap_freetuple(newtup); + + heap_close(rel, RowExclusiveLock); +} + +void +AlterRelationSecLabel(RangeVar *relation, const char *attname, + ObjectType objtype, DefElem *seclabel) +{ + Oid relid; + char relkind; + + /* Check relation type against type specified in the ALTER command */ + relid = RangeVarGetRelid(relation, false); + relkind = get_rel_relkind(relid); + + switch (objtype) + { + case OBJECT_TABLE: + case OBJECT_COLUMN: + if (relkind != RELKIND_RELATION) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", get_rel_name(relid)))); + break; + + case OBJECT_SEQUENCE: + if (relkind != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a sequence", get_rel_name(relid)))); + break; + + default: + elog(ERROR, "unrecognized object type: %d", (int)objtype); + break; + } + + /* Exec set security label */ + if (objtype != OBJECT_COLUMN) + ExecRelationSetSecLabel(relid, seclabel); + else + ExecAttributeSetSecLabel(relid, attname, seclabel); +} /* * This code supports diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index f432e74..469b5cb 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -33,6 +33,7 @@ #include "nodes/makefuncs.h" #include "parser/parse_func.h" #include "pgstat.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "tcop/utility.h" #include "utils/acl.h" @@ -182,6 +183,10 @@ CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid, bool checkPermissions) NameListToString(stmt->funcname)))); } + /* SELinux checks */ + if (checkPermissions) + sepgsql_trigger_create(RelationGetRelid(rel), stmt->trigname, funcoid); + /* * If the command is a user-entered CREATE CONSTRAINT TRIGGER command that * references one of the built-in RI_FKey trigger functions, assume it is @@ -746,6 +751,7 @@ DropTrigger(Oid relid, const char *trigname, DropBehavior behavior, if (!pg_class_ownercheck(relid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, get_rel_name(relid)); + sepgsql_trigger_drop(relid, trigname); object.classId = TriggerRelationId; object.objectId = HeapTupleGetOid(tup); @@ -862,6 +868,9 @@ renametrig(Oid relid, */ targetrel = heap_open(relid, AccessExclusiveLock); + /* SELinux checks */ + sepgsql_trigger_alter(relid, oldname); + /* * Scan pg_trigger twice for existing triggers on relation. We do this in * order to ensure a trigger does not exist with newname (The unique index diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index a1f301b..16cfa5d 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -35,6 +35,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_func.h" +#include "security/sepgsql.h" #include "tsearch/ts_cache.h" #include "tsearch/ts_public.h" #include "tsearch/ts_utils.h" @@ -171,6 +172,7 @@ DefineTSParser(List *names, List *parameters) NameData pname; Oid prsOid; Oid namespaceoid; + Oid secid; if (!superuser()) ereport(ERROR, @@ -250,12 +252,22 @@ DefineTSParser(List *names, List *parameters) (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("text search parser lextypes method is required"))); + /* Permission checks */ + secid = sepgsql_ts_parser_create(prsname, namespaceoid, + DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1]), + DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1]), + DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1]), + DatumGetObjectId(values[Anum_pg_ts_parser_prsheadline - 1]), + DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1])); + /* * Looks good, insert */ prsRel = heap_open(TSParserRelationId, RowExclusiveLock); tup = heap_form_tuple(prsRel->rd_att, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, secid); prsOid = simple_heap_insert(prsRel, tup); @@ -372,6 +384,9 @@ RenameTSParser(List *oldname, const char *newname) prsId = TSParserGetPrsid(oldname, false); + /* SELinux checks */ + sepgsql_ts_parser_alter(prsId, newname); + tup = SearchSysCacheCopy(TSPARSEROID, ObjectIdGetDatum(prsId), 0, 0, 0); @@ -503,6 +518,7 @@ DefineTSDictionary(List *names, List *parameters) List *dictoptions = NIL; Oid dictOid; Oid namespaceoid; + Oid secid; AclResult aclresult; char *dictname; @@ -515,6 +531,9 @@ DefineTSDictionary(List *names, List *parameters) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceoid)); + /* SELinux check */ + secid = sepgsql_ts_dict_create(dictname, namespaceoid); + /* * loop over the definition list and extract the information we need. */ @@ -563,6 +582,8 @@ DefineTSDictionary(List *names, List *parameters) dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock); tup = heap_form_tuple(dictRel->rd_att, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, secid); dictOid = simple_heap_insert(dictRel, tup); @@ -621,6 +642,9 @@ RenameTSDictionary(List *oldname, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* SELinux checks */ + sepgsql_ts_dict_alter(dictId, newname); + namestrcpy(&(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); @@ -762,6 +786,9 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY, NameListToString(stmt->dictname)); + /* SELinux checks */ + sepgsql_ts_dict_alter(dictId, NULL); + /* deserialize the existing set of options */ opt = SysCacheGetAttr(TSDICTOID, tup, Anum_pg_ts_dict_dictinitoption, @@ -889,6 +916,8 @@ AlterTSDictionaryOwner(List *name, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); } + /* SELinux checks */ + sepgsql_ts_dict_alter(dictId, NULL); form->dictowner = newOwnerId; @@ -999,6 +1028,7 @@ DefineTSTemplate(List *names, List *parameters) NameData dname; int i; Oid dictOid; + Oid dictSecid; Oid namespaceoid; char *tmplname; @@ -1054,6 +1084,11 @@ DefineTSTemplate(List *names, List *parameters) (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("text search template lexize method is required"))); + /* SELinux checks */ + dictSecid = sepgsql_ts_template_create(tmplname, namespaceoid, + DatumGetObjectId(values[Anum_pg_ts_template_tmplinit - 1]), + DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1])); + /* * Looks good, insert */ @@ -1061,6 +1096,8 @@ DefineTSTemplate(List *names, List *parameters) tmplRel = heap_open(TSTemplateRelationId, RowExclusiveLock); tup = heap_form_tuple(tmplRel->rd_att, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, dictSecid); dictOid = simple_heap_insert(tmplRel, tup); @@ -1093,6 +1130,9 @@ RenameTSTemplate(List *oldname, const char *newname) tmplId = TSTemplateGetTmplid(oldname, false); + /* Permission checks */ + sepgsql_ts_template_alter(tmplId, newname); + tup = SearchSysCacheCopy(TSTEMPLATEOID, ObjectIdGetDatum(tmplId), 0, 0, 0); @@ -1335,6 +1375,7 @@ DefineTSConfiguration(List *names, List *parameters) Oid sourceOid = InvalidOid; Oid prsOid = InvalidOid; Oid cfgOid; + Oid cfgSecid; ListCell *pl; /* Convert list of names to a name and namespace */ @@ -1399,6 +1440,9 @@ DefineTSConfiguration(List *names, List *parameters) (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("text search parser is required"))); + /* SELinux checks */ + cfgSecid = sepgsql_ts_config_create(cfgname, namespaceoid); + /* * Looks good, build tuple and insert */ @@ -1414,6 +1458,8 @@ DefineTSConfiguration(List *names, List *parameters) cfgRel = heap_open(TSConfigRelationId, RowExclusiveLock); tup = heap_form_tuple(cfgRel->rd_att, values, nulls); + if (HeapTupleHasSecid(tup)) + HeapTupleSetSecid(tup, cfgSecid); cfgOid = simple_heap_insert(cfgRel, tup); @@ -1519,6 +1565,9 @@ RenameTSConfiguration(List *oldname, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* permission checks */ + sepgsql_ts_config_alter(cfgId, newname); + namestrcpy(&(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); @@ -1690,6 +1739,8 @@ AlterTSConfigurationOwner(List *name, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); } + /* SELinux checks */ + sepgsql_ts_config_alter(cfgId, NULL); form->cfgowner = newOwnerId; @@ -1727,6 +1778,9 @@ AlterTSConfiguration(AlterTSConfigurationStmt *stmt) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION, NameListToString(stmt->cfgname)); + /* SELinux checks */ + sepgsql_ts_config_alter(HeapTupleGetOid(tup), NULL); + relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock); /* Add or drop mappings */ diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 528a917..e10b914 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -56,6 +56,7 @@ #include "parser/parse_expr.h" #include "parser/parse_func.h" #include "parser/parse_type.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -1543,6 +1544,7 @@ AlterDomainDefault(List *names, Node *defaultRaw) /* Check it's a domain and check user has permission for ALTER DOMAIN */ checkDomainOwner(tup, typename); + sepgsql_type_alter(domainoid, NULL, InvalidOid); /* Setup new tuple */ MemSet(new_record, (Datum) 0, sizeof(new_record)); @@ -1671,6 +1673,7 @@ AlterDomainNotNull(List *names, bool notNull) /* Check it's a domain and check user has permission for ALTER DOMAIN */ checkDomainOwner(tup, typename); + sepgsql_type_alter(domainoid, NULL, InvalidOid); /* Is the domain already set to the desired constraint? */ if (typTup->typnotnull == notNull) @@ -1772,6 +1775,7 @@ AlterDomainDropConstraint(List *names, const char *constrName, /* Check it's a domain and check user has permission for ALTER DOMAIN */ checkDomainOwner(tup, typename); + sepgsql_type_alter(domainoid, NULL, InvalidOid); /* Grab an appropriate lock on the pg_constraint relation */ conrel = heap_open(ConstraintRelationId, RowExclusiveLock); @@ -1848,6 +1852,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint) /* Check it's a domain and check user has permission for ALTER DOMAIN */ checkDomainOwner(tup, typename); + sepgsql_type_alter(domainoid, NULL, InvalidOid); /* Check for unsupported constraint types */ if (IsA(newConstraint, FkConstraint)) @@ -2470,6 +2475,9 @@ RenameType(List *names, const char *newTypeName) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, format_type_be(typeOid)); + /* SELinux check permission */ + sepgsql_type_alter(typeOid, newTypeName, InvalidOid); + /* * If it's a composite type, we need to check that it really is a * free-standing composite type, and not a table's rowtype. We want people @@ -2590,6 +2598,8 @@ AlterTypeOwner(List *names, Oid newOwnerId) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(typTup->typnamespace)); } + /* SELinux checks permissions */ + sepgsql_type_alter(HeapTupleGetOid(tup), NULL, InvalidOid); /* * If it's a composite type, invoke ATExecChangeOwner so that we fix @@ -2706,6 +2716,9 @@ AlterTypeNamespace(List *names, const char *newschema) errhint("You can alter type %s, which will alter the array type as well.", format_type_be(elemOid)))); + /* SELinux checks permissions */ + sepgsql_type_alter(typeOid, NULL, nspOid); + /* and do the work */ AlterTypeNamespaceInternal(typeOid, nspOid, false, true); } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index dcb30f8..737b58a 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -32,6 +32,7 @@ #include "catalog/namespace.h" #include "catalog/pg_database.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_security.h" #include "catalog/storage.h" #include "commands/dbcommands.h" #include "commands/vacuum.h" @@ -1209,6 +1210,9 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound, /* all done with this class, but hold lock until commit */ relation_close(onerel, NoLock); + /* Also reclaim orphan security label */ + seclabelRelationReclaim(relid); + /* * Complete the transaction and free all temporary memory used. */ diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index dfbce72..1bfab03 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -28,6 +28,7 @@ #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteManip.h" #include "rewrite/rewriteSupport.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -166,6 +167,9 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel)); + /* SELinux checks */ + sepgsql_view_replace(viewOid); + /* Also check it's not in use already */ CheckTableNotInUse(rel, "CREATE OR REPLACE VIEW"); diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c index 564347f..154b3c4 100644 --- a/src/backend/executor/execJunk.c +++ b/src/backend/executor/execJunk.c @@ -60,7 +60,8 @@ * An optional resultSlot can be passed as well. */ JunkFilter * -ExecInitJunkFilter(List *targetList, bool hasoid, TupleTableSlot *slot) +ExecInitJunkFilter(List *targetList, bool hasoid, bool hasseclabel, + TupleTableSlot *slot) { JunkFilter *junkfilter; TupleDesc cleanTupType; @@ -72,7 +73,7 @@ ExecInitJunkFilter(List *targetList, bool hasoid, TupleTableSlot *slot) /* * Compute the tuple descriptor for the cleaned tuple. */ - cleanTupType = ExecCleanTypeFromTL(targetList, hasoid); + cleanTupType = ExecCleanTypeFromTL(targetList, hasoid, hasseclabel); /* * Use the given slot, or make a new slot if we weren't given one. diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 131be22..5c4205b 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -39,6 +39,7 @@ #include "access/xact.h" #include "catalog/heap.h" #include "catalog/namespace.h" +#include "catalog/pg_security.h" #include "catalog/toasting.h" #include "commands/tablespace.h" #include "commands/trigger.h" @@ -50,6 +51,7 @@ #include "optimizer/clauses.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/smgr.h" @@ -442,7 +444,10 @@ ExecCheckRTPerms(List *rangeTable) foreach(l, rangeTable) { - ExecCheckRTEPerms((RangeTblEntry *) lfirst(l)); + RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); + + ExecCheckRTEPerms(rte); + sepgsqlCheckRTEPerms(rte); } } @@ -901,16 +906,16 @@ InitPlan(QueryDesc *queryDesc, int eflags) for (i = 0; i < as_nplans; i++) { PlanState *subplan = appendplans[i]; + Relation resultRel = resultRelInfo->ri_RelationDesc; JunkFilter *j; if (operation == CMD_UPDATE) - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - subplan->plan->targetlist); + ExecCheckPlanOutput(resultRel, subplan->plan->targetlist); j = ExecInitJunkFilter(subplan->plan->targetlist, - resultRelInfo->ri_RelationDesc->rd_att->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - + RelationGetDescr(resultRel)->tdhasoid, + RelationGetDescr(resultRel)->tdhassecid, + ExecAllocTableSlot(estate->es_tupleTable)); /* * Since it must be UPDATE/DELETE, there had better be a * "ctid" junk attribute in the tlist ... but ctid could @@ -953,6 +958,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) j = ExecInitJunkFilter(planstate->plan->targetlist, tupType->tdhasoid, + tupType->tdhassecid, ExecAllocTableSlot(estate->es_tupleTable)); estate->es_junkFilter = j; if (estate->es_result_relation_info) @@ -1023,7 +1029,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) * We assume all the sublists will generate the same output tupdesc. */ tupType = ExecTypeFromTL((List *) linitial(plannedstmt->returningLists), - false); + false, false); /* Set up a slot for the output of the RETURNING projection(s) */ slot = ExecAllocTableSlot(estate->es_tupleTable); @@ -1346,6 +1352,37 @@ ExecContextForcesOids(PlanState *planstate, bool *hasoids) return false; } +/* + * ExecContextForcesSecids + * + * We need to ensure that result tuples have space for security identifier. + * if the security feature need to store it within the given relation. + */ +bool ExecContextForcesSecids(PlanState *planstate, bool *hassecid) +{ + if (planstate->state->es_select_into) + { + *hassecid = securityTupleDescHasSecid(InvalidOid, + RELKIND_RELATION); + return true; + } + else + { + ResultRelInfo *ri = planstate->state->es_result_relation_info; + + if (ri && ri->ri_RelationDesc) + { + Oid relid = RelationGetRelid(ri->ri_RelationDesc); + char relkind = RelationGetForm(ri->ri_RelationDesc)->relkind; + + *hassecid = securityTupleDescHasSecid(relid, relkind); + + return true; + } + } + return false; +} + /* ---------------------------------------------------------------- * ExecEndPlan * @@ -1426,6 +1463,58 @@ ExecEndPlan(PlanState *planstate, EState *estate) } } +/* + * fetchWritableSystemAttribute() fetches writable system column data + * using Junkfilter, and saves them at TupleTableSlot temporary. + * + * storeWritableSystemAttribute() copies these fetched data into + * header structure of HeapTuple. + */ +static void +fetchWritableSystemAttribute(JunkFilter *junkfilter, TupleTableSlot *slot, + Datum *tts_seclabel) +{ + AttrNumber attno; + Datum datum; + bool isnull; + + /* for Security Label */ + attno = ExecFindJunkAttribute(junkfilter, SecurityAttributeName); + if (attno != InvalidAttrNumber) + { + datum = ExecGetJunkAttribute(slot, attno, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unable to set NULL on \"%s\"", + SecurityAttributeName))); + *tts_seclabel = datum; + } +} + +static void +storeWritableSystemAttribute(Relation rel, TupleTableSlot *slot, HeapTuple tuple) +{ + Oid relid = RelationGetRelid(rel); + Oid secid; + + /* "security_label" */ + if (DatumGetPointer(slot->tts_seclabel) != NULL) + { + char *seclabel = TextDatumGetCString(slot->tts_seclabel); + + if (!HeapTupleHasSecid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unable to assign security label on \"%s\"", + RelationGetRelationName(rel)))); + secid = securityTransSecLabelIn(relid, seclabel); + HeapTupleSetSecid(tuple, secid); + } + else if (HeapTupleHasSecid(tuple)) + HeapTupleSetSecid(tuple, InvalidOid); +} + /* ---------------------------------------------------------------- * ExecutePlan * @@ -1487,6 +1576,8 @@ ExecutePlan(EState *estate, */ for (;;) { + Datum tts_seclabel = PointerGetDatum(NULL); + /* Reset the per-output-tuple exprcontext */ ResetPerTupleExprContext(estate); @@ -1631,6 +1722,11 @@ lnext: ; } /* + * extract writable system attribute + */ + fetchWritableSystemAttribute(junkfilter, slot, &tts_seclabel); + + /* * extract the 'ctid' junk attribute. */ if (operation == CMD_UPDATE || operation == CMD_DELETE) @@ -1657,6 +1753,7 @@ lnext: ; if (operation != CMD_DELETE) slot = ExecFilterJunk(junkfilter, slot); } + slot->tts_seclabel = tts_seclabel; /* * now that we have a tuple, do the appropriate thing with it.. either @@ -1781,6 +1878,8 @@ ExecInsert(TupleTableSlot *slot, if (resultRelationDesc->rd_rel->relhasoids) HeapTupleSetOid(tuple, InvalidOid); + storeWritableSystemAttribute(resultRelationDesc, slot, tuple); + /* BEFORE ROW INSERT Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) @@ -1811,6 +1910,12 @@ ExecInsert(TupleTableSlot *slot, } /* + * SELinux assigns default security label, and + * it also checks db_tuple:{insert} permission + */ + sepgsqlHeapTupleInsert(resultRelationDesc, tuple, false); + + /* * Check the constraints of the tuple */ if (resultRelationDesc->rd_att->constr) @@ -2018,6 +2123,8 @@ ExecUpdate(TupleTableSlot *slot, resultRelInfo = estate->es_result_relation_info; resultRelationDesc = resultRelInfo->ri_RelationDesc; + storeWritableSystemAttribute(resultRelationDesc, slot, tuple); + /* BEFORE ROW UPDATE Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0) @@ -2048,6 +2155,9 @@ ExecUpdate(TupleTableSlot *slot, } } + /* SELinux checks db_tuple:{relabelfrom relabelto}, if needed */ + sepgsqlHeapTupleUpdate(resultRelationDesc, tupleid, tuple); + /* * Check the constraints of the tuple * @@ -2843,6 +2953,7 @@ OpenIntoRel(QueryDesc *queryDesc) Oid namespaceId; Oid tablespaceId; Datum reloptions; + Oid *secLabels; AclResult aclresult; Oid intoRelationId; TupleDesc tupdesc; @@ -2886,6 +2997,14 @@ OpenIntoRel(QueryDesc *queryDesc) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceId)); + /* SELinux checks */ + secLabels = sepgsql_relation_create(intoName, + RELKIND_RELATION, + queryDesc->tupDesc, + namespaceId, + NULL, NIL, + true, true); + /* * Select tablespace to use. If not specified, use default tablespace * (which may in turn default to database's default). @@ -2944,7 +3063,8 @@ OpenIntoRel(QueryDesc *queryDesc) 0, into->onCommit, reloptions, - allowSystemTableMods); + allowSystemTableMods, + secLabels); FreeTupleDesc(tupdesc); @@ -3069,6 +3189,10 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self) if (myState->rel->rd_rel->relhasoids) HeapTupleSetOid(tuple, InvalidOid); + storeWritableSystemAttribute(myState->rel, slot, tuple); + /* SELinux checks db_tuple:{insert} */ + sepgsqlHeapTupleInsert(myState->rel, tuple, false); + heap_insert(myState->rel, tuple, myState->estate->es_output_cid, diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 119ddbc..66f918e 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -47,6 +47,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/planner.h" #include "pgstat.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -1034,6 +1035,7 @@ init_fcache(Oid foid, FuncExprState *fcache, aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid)); + sepgsql_proc_execute(foid); /* * Safety check on nargs. Under normal circumstances this should never @@ -4032,6 +4034,7 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate, if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(acoerce->elemfuncid)); + sepgsql_proc_execute(acoerce->elemfuncid); /* Set up the primary fmgr lookup information */ fmgr_info_cxt(acoerce->elemfuncid, &(astate->elemfunc), diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 19fa4e6..44021f2 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -20,6 +20,7 @@ #include "executor/executor.h" #include "miscadmin.h" +#include "security/rowlevel.h" #include "utils/memutils.h" @@ -53,6 +54,7 @@ ExecScan(ScanState *node, ProjectionInfo *projInfo; ExprDoneCond isDone; TupleTableSlot *resultSlot; + Scan *scan = (Scan *)node->ps.plan; /* * Fetch data from node @@ -64,7 +66,7 @@ ExecScan(ScanState *node, * If we have neither a qual to check nor a projection to do, just skip * all the overhead and return the raw scan tuple. */ - if (!qual && !projInfo) + if (!qual && !projInfo && !scan->rowlvPerms) return (*accessMtd) (node); /* @@ -128,9 +130,18 @@ ExecScan(ScanState *node, * when the qual is nil ... saves only a few cycles, but they add up * ... */ - if (!qual || ExecQual(qual, econtext, false)) + if (rowlvExecScanFilter(scan, node->ss_currentRelation, slot) + && (!qual || ExecQual(qual, econtext, false))) { /* + * NOTE: On FK checks, the Row-level feature needs to raise + * an error after evaluation of all the given quals to avoid + * incorrect error reporting. We assume FK implementation + * does not use malicious functions as the quals. + */ + rowlvExecScanAbort(scan, node->ss_currentRelation, slot); + + /* * Found a satisfactory scan tuple. */ if (projInfo) @@ -197,6 +208,7 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc int numattrs = tupdesc->natts; int attrno; bool hasoid; + bool hassecid; ListCell *tlist_item = list_head(tlist); /* Check the tlist attributes */ @@ -240,12 +252,16 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc return false; /* tlist too long */ /* - * If the plan context requires a particular hasoid setting, then that has - * to match, too. + * If the plan context requires a particular hasoid or hassecid setting, + * then that has to match, too. */ if (ExecContextForcesOids(ps, &hasoid) && hasoid != tupdesc->tdhasoid) return false; + if (ExecContextForcesSecids(ps, &hassecid) && + hassecid != tupdesc->tdhassecid) + return false; + return true; } diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 06142c9..c5f614a 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -100,7 +100,7 @@ static TupleDesc ExecTypeFromTLInternal(List *targetList, - bool hasoid, bool skipjunk); + bool hasoid, bool hasseclabel, bool skipjunk); /* ---------------------------------------------------------------- @@ -968,9 +968,9 @@ ExecInitNullTupleSlot(EState *estate, TupleDesc tupType) * ---------------------------------------------------------------- */ TupleDesc -ExecTypeFromTL(List *targetList, bool hasoid) +ExecTypeFromTL(List *targetList, bool hasoid, bool hassecid) { - return ExecTypeFromTLInternal(targetList, hasoid, false); + return ExecTypeFromTLInternal(targetList, hasoid, hassecid, false); } /* ---------------------------------------------------------------- @@ -980,13 +980,14 @@ ExecTypeFromTL(List *targetList, bool hasoid) * ---------------------------------------------------------------- */ TupleDesc -ExecCleanTypeFromTL(List *targetList, bool hasoid) +ExecCleanTypeFromTL(List *targetList, bool hasoid, bool hassecid) { - return ExecTypeFromTLInternal(targetList, hasoid, true); + return ExecTypeFromTLInternal(targetList, hasoid, hassecid, true); } static TupleDesc -ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk) +ExecTypeFromTLInternal(List *targetList, bool hasoid, + bool hassecid, bool skipjunk) { TupleDesc typeInfo; ListCell *l; @@ -998,6 +999,7 @@ ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk) else len = ExecTargetListLength(targetList); typeInfo = CreateTemplateTupleDesc(len, hasoid); + typeInfo->tdhassecid = hassecid; foreach(l, targetList) { diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 7033189..34faaeb 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -512,6 +512,7 @@ void ExecAssignResultTypeFromTL(PlanState *planstate) { bool hasoid; + bool hassecid; TupleDesc tupDesc; if (ExecContextForcesOids(planstate, &hasoid)) @@ -524,12 +525,15 @@ ExecAssignResultTypeFromTL(PlanState *planstate) hasoid = false; } + if (!ExecContextForcesSecids(planstate, &hassecid)) + hassecid = false; + /* * ExecTypeFromTL needs the parse-time representation of the tlist, not a * list of ExprStates. This is good because some plan nodes don't bother * to set up planstate->targetlist ... */ - tupDesc = ExecTypeFromTL(planstate->plan->targetlist, hasoid); + tupDesc = ExecTypeFromTL(planstate->plan->targetlist, hasoid, hassecid); ExecAssignResultType(planstate, tupDesc); } diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 1d679a9..8b46999 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -1135,7 +1135,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, /* Set up junk filter if needed */ if (junkFilter) - *junkFilter = ExecInitJunkFilter(tlist, false, NULL); + *junkFilter = ExecInitJunkFilter(tlist, false, false, NULL); } else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID) { @@ -1167,7 +1167,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, COERCE_DONTCARE); /* Set up junk filter if needed */ if (junkFilter) - *junkFilter = ExecInitJunkFilter(tlist, false, NULL); + *junkFilter = ExecInitJunkFilter(tlist, false, false, NULL); return false; /* NOT returning whole tuple */ } } @@ -1180,7 +1180,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, * what the caller expects will happen at runtime. */ if (junkFilter) - *junkFilter = ExecInitJunkFilter(tlist, false, NULL); + *junkFilter = ExecInitJunkFilter(tlist, false, false, NULL); return true; } Assert(tupdesc); diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index d7cccc5..6ba72ca 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -81,6 +81,7 @@ #include "parser/parse_agg.h" #include "parser/parse_coerce.h" #include "parser/parse_oper.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -1431,6 +1432,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(aggref->aggfnoid)); + sepgsql_proc_execute(aggref->aggfnoid); peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn; peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn; @@ -1454,6 +1456,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(transfn_oid)); + sepgsql_proc_execute(transfn_oid); if (OidIsValid(finalfn_oid)) { aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner, @@ -1461,6 +1464,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(finalfn_oid)); + sepgsql_proc_execute(finalfn_oid); } } diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c index b6143e6..4394855 100644 --- a/src/backend/executor/nodeMergejoin.c +++ b/src/backend/executor/nodeMergejoin.c @@ -98,6 +98,7 @@ #include "executor/execdefs.h" #include "executor/nodeMergejoin.h" #include "miscadmin.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -215,6 +216,7 @@ MJExamineQuals(List *mergeclauses, if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(cmpproc)); + sepgsql_proc_execute(cmpproc); /* Set up the fmgr lookup information */ fmgr_info(cmpproc, &(clause->cmpfinfo)); diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index aff7a63..41dede3 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -869,7 +869,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) * (hack alert!). The righthand expressions will be evaluated in our * own innerecontext. */ - tupDesc = ExecTypeFromTL(leftptlist, false); + tupDesc = ExecTypeFromTL(leftptlist, false, false); slot = ExecAllocTableSlot(tupTable); ExecSetSlotDescriptor(slot, tupDesc); sstate->projLeft = ExecBuildProjectionInfo(lefttlist, @@ -877,7 +877,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) slot, NULL); - tupDesc = ExecTypeFromTL(rightptlist, false); + tupDesc = ExecTypeFromTL(rightptlist, false, false); slot = ExecAllocTableSlot(tupTable); ExecSetSlotDescriptor(slot, tupDesc); sstate->projRight = ExecBuildProjectionInfo(righttlist, diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 6674f67..fec5c4a 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -43,6 +43,7 @@ #include "optimizer/clauses.h" #include "parser/parse_agg.h" #include "parser/parse_coerce.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/datum.h" @@ -1224,6 +1225,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(wfunc->winfnoid)); + sepgsql_proc_execute(wfunc->winfnoid); /* Fill in the perfuncstate data */ perfuncstate->wfuncstate = wfuncstate; @@ -1418,6 +1420,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc, if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(transfn_oid)); + sepgsql_proc_execute(transfn_oid); if (OidIsValid(finalfn_oid)) { aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner, @@ -1425,6 +1428,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc, if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(finalfn_oid)); + sepgsql_proc_execute(finalfn_oid); } } diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 4cd7b0b..f661849 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -705,6 +705,8 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum, mtuple->t_tableOid = tuple->t_tableOid; if (rel->rd_att->tdhasoid) HeapTupleSetOid(mtuple, HeapTupleGetOid(tuple)); + if (HeapTupleHasSecid(mtuple)) + HeapTupleSetSecid(mtuple, HeapTupleGetSecid(tuple)); } else { diff --git a/src/backend/libpq/be-fsstubs.c b/src/backend/libpq/be-fsstubs.c index b6c46c1..6445ea8 100644 --- a/src/backend/libpq/be-fsstubs.c +++ b/src/backend/libpq/be-fsstubs.c @@ -46,6 +46,7 @@ #include "libpq/be-fsstubs.h" #include "libpq/libpq-fs.h" #include "miscadmin.h" +#include "security/sepgsql.h" #include "storage/fd.h" #include "storage/large_object.h" #include "utils/acl.h" @@ -173,6 +174,9 @@ lo_read(int fd, char *buf, int len) errmsg("permission denied for large object %u", cookies[fd]->id))); + /* SELinux db_blob:{read} checks */ + sepgsql_largeobject_read(cookies[fd]->id, cookies[fd]->snapshot); + status = inv_read(cookies[fd], buf, len); return status; @@ -205,6 +209,9 @@ lo_write(int fd, const char *buf, int len) errmsg("permission denied for large object %u", cookies[fd]->id))); + /* SELinux db_blob:{write} */ + sepgsql_largeobject_write(cookies[fd]->id, cookies[fd]->snapshot); + status = inv_write(cookies[fd], buf, len); return status; @@ -233,6 +240,10 @@ Datum lo_creat(PG_FUNCTION_ARGS) { Oid lobjId; + Oid secid; + + /* SELinux: db_blob:{create} */ + secid = sepgsql_largeobject_create(InvalidOid, NULL); /* * We don't actually need to store into fscxt, but create it anyway to @@ -240,7 +251,7 @@ lo_creat(PG_FUNCTION_ARGS) */ CreateFSContext(); - lobjId = inv_create(InvalidOid); + lobjId = inv_create(InvalidOid, secid); PG_RETURN_OID(lobjId); } @@ -249,6 +260,10 @@ Datum lo_create(PG_FUNCTION_ARGS) { Oid lobjId = PG_GETARG_OID(0); + Oid secid; + + /* SELinux: db_blob:{create} */ + secid = sepgsql_largeobject_create(lobjId, NULL); /* * We don't actually need to store into fscxt, but create it anyway to @@ -256,7 +271,7 @@ lo_create(PG_FUNCTION_ARGS) */ CreateFSContext(); - lobjId = inv_create(lobjId); + lobjId = inv_create(lobjId, secid); PG_RETURN_OID(lobjId); } @@ -286,6 +301,9 @@ lo_unlink(PG_FUNCTION_ARGS) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be owner of large object %u", lobjId))); + /* SELinux: db_blob:{drop} */ + sepgsql_largeobject_drop(lobjId); + /* * If there are any open LO FDs referencing that ID, close 'em. */ @@ -381,9 +399,10 @@ lo_import_internal(text *filename, Oid lobjOid) int nbytes, tmp; char buf[BUFSIZE]; - char fnamebuf[MAXPGPATH]; + char *fnamebuf = text_to_cstring(filename); LargeObjectDesc *lobj; Oid oid; + Oid secid; #ifndef ALLOW_DANGEROUS_LO_FUNCTIONS if (!superuser()) @@ -392,13 +411,14 @@ lo_import_internal(text *filename, Oid lobjOid) errmsg("must be superuser to use server-side lo_import()"), errhint("Anyone can use the client-side lo_import() provided by libpq."))); #endif + /* SELinux: db_blob:{create import} */ + secid = sepgsql_largeobject_import(lobjOid, fnamebuf); CreateFSContext(); /* * open the file to be read in */ - text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf)); fd = PathNameOpenFile(fnamebuf, O_RDONLY | PG_BINARY, 0666); if (fd < 0) ereport(ERROR, @@ -409,7 +429,7 @@ lo_import_internal(text *filename, Oid lobjOid) /* * create an inversion object */ - oid = inv_create(lobjOid); + oid = inv_create(lobjOid, secid); /* * read in from the filesystem and write to the inversion object @@ -447,7 +467,7 @@ lo_export(PG_FUNCTION_ARGS) int nbytes, tmp; char buf[BUFSIZE]; - char fnamebuf[MAXPGPATH]; + char *fnamebuf = text_to_cstring(filename); LargeObjectDesc *lobj; mode_t oumask; @@ -458,6 +478,8 @@ lo_export(PG_FUNCTION_ARGS) errmsg("must be superuser to use server-side lo_export()"), errhint("Anyone can use the client-side lo_export() provided by libpq."))); #endif + /* SELinux: db_blob:{read export} */ + sepgsql_largeobject_export(lobjId, fnamebuf); CreateFSContext(); @@ -528,6 +550,9 @@ lo_truncate(PG_FUNCTION_ARGS) errmsg("permission denied for large object %u", cookies[fd]->id))); + /* SELinux: db_blob:{write} */ + sepgsql_largeobject_write(cookies[fd]->id, cookies[fd]->snapshot); + inv_truncate(cookies[fd], len); PG_RETURN_INT32(0); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 13c82a6..ca21326 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -259,6 +259,7 @@ CopyScanFields(Scan *from, Scan *newnode) CopyPlanFields((Plan *) from, (Plan *) newnode); COPY_SCALAR_FIELD(scanrelid); + COPY_SCALAR_FIELD(rowlvPerms); } /* @@ -2075,6 +2076,7 @@ _copyColumnDef(ColumnDef *from) COPY_NODE_FIELD(raw_default); COPY_NODE_FIELD(cooked_default); COPY_NODE_FIELD(constraints); + COPY_NODE_FIELD(secLabel); return newnode; } @@ -2414,6 +2416,7 @@ _copyCreateStmt(CreateStmt *from) COPY_NODE_FIELD(options); COPY_SCALAR_FIELD(oncommit); COPY_STRING_FIELD(tablespacename); + COPY_NODE_FIELD(secLabel); return newnode; } @@ -2638,6 +2641,21 @@ _copyAlterOwnerStmt(AlterOwnerStmt *from) return newnode; } +static AlterSecLabelStmt * +_copyAlterSecLabelStmt(AlterSecLabelStmt *from) +{ + AlterSecLabelStmt *newnode = makeNode(AlterSecLabelStmt); + + COPY_SCALAR_FIELD(objectType); + COPY_NODE_FIELD(relation); + COPY_NODE_FIELD(object); + COPY_NODE_FIELD(objarg); + COPY_STRING_FIELD(subname); + COPY_NODE_FIELD(secLabel); + + return newnode; +} + static RuleStmt * _copyRuleStmt(RuleStmt *from) { @@ -2887,6 +2905,7 @@ _copyCreateSeqStmt(CreateSeqStmt *from) COPY_NODE_FIELD(sequence); COPY_NODE_FIELD(options); + COPY_NODE_FIELD(secLabel); return newnode; } @@ -3819,6 +3838,9 @@ copyObject(void *from) case T_AlterOwnerStmt: retval = _copyAlterOwnerStmt(from); break; + case T_AlterSecLabelStmt: + retval = _copyAlterSecLabelStmt(from); + break; case T_RuleStmt: retval = _copyRuleStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 06a06b3..0752771 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1078,6 +1078,7 @@ _equalCreateStmt(CreateStmt *a, CreateStmt *b) COMPARE_NODE_FIELD(options); COMPARE_SCALAR_FIELD(oncommit); COMPARE_STRING_FIELD(tablespacename); + COMPARE_NODE_FIELD(secLabel); return true; } @@ -1271,6 +1272,19 @@ _equalAlterOwnerStmt(AlterOwnerStmt *a, AlterOwnerStmt *b) } static bool +_equalAlterSecLabelStmt(AlterSecLabelStmt *a, AlterSecLabelStmt *b) +{ + COMPARE_SCALAR_FIELD(objectType); + COMPARE_NODE_FIELD(relation); + COMPARE_NODE_FIELD(object); + COMPARE_NODE_FIELD(objarg); + COMPARE_STRING_FIELD(subname); + COMPARE_NODE_FIELD(secLabel); + + return true; +} + +static bool _equalRuleStmt(RuleStmt *a, RuleStmt *b) { COMPARE_NODE_FIELD(relation); @@ -1477,6 +1491,7 @@ _equalCreateSeqStmt(CreateSeqStmt *a, CreateSeqStmt *b) { COMPARE_NODE_FIELD(sequence); COMPARE_NODE_FIELD(options); + COMPARE_NODE_FIELD(secLabel); return true; } @@ -2054,6 +2069,7 @@ _equalColumnDef(ColumnDef *a, ColumnDef *b) COMPARE_NODE_FIELD(raw_default); COMPARE_NODE_FIELD(cooked_default); COMPARE_NODE_FIELD(constraints); + COMPARE_NODE_FIELD(secLabel); return true; } @@ -2596,6 +2612,9 @@ equal(void *a, void *b) case T_AlterOwnerStmt: retval = _equalAlterOwnerStmt(a, b); break; + case T_AlterSecLabelStmt: + retval = _equalAlterSecLabelStmt(a, b); + break; case T_RuleStmt: retval = _equalRuleStmt(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 1c8691a..ffc2f19 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -285,6 +285,7 @@ _outScanInfo(StringInfo str, Scan *node) _outPlanInfo(str, (Plan *) node); WRITE_UINT_FIELD(scanrelid); + WRITE_UINT_FIELD(rowlvPerms); } /* @@ -1534,6 +1535,7 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node) WRITE_BOOL_FIELD(has_eclass_joins); WRITE_BITMAPSET_FIELD(index_outer_relids); WRITE_NODE_FIELD(index_inner_paths); + WRITE_UINT_FIELD(rowlvPerms); } static void @@ -1717,6 +1719,7 @@ _outCreateStmt(StringInfo str, CreateStmt *node) WRITE_NODE_FIELD(options); WRITE_ENUM_FIELD(oncommit, OnCommitAction); WRITE_STRING_FIELD(tablespacename); + WRITE_NODE_FIELD(secLabel); } static void @@ -1839,6 +1842,7 @@ _outColumnDef(StringInfo str, ColumnDef *node) WRITE_NODE_FIELD(raw_default); WRITE_NODE_FIELD(cooked_default); WRITE_NODE_FIELD(constraints); + WRITE_NODE_FIELD(secLabel); } static void diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 6e5c251..8156719 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -305,6 +305,9 @@ create_scan_plan(PlannerInfo *root, Path *best_path) break; } + /* Copy of row-level permissions to Scan node */ + ((Scan *)plan)->rowlvPerms = rel->rowlvPerms; + /* * If there are any pseudoconstant clauses attached to this node, insert a * gating Result node that evaluates the pseudoconstants as one-time diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index be75590..694a40c 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -38,6 +38,7 @@ #include "parser/parse_coerce.h" #include "parser/parse_func.h" #include "rewrite/rewriteManip.h" +#include "security/sepgsql.h" #include "tcop/tcopprot.h" #include "utils/acl.h" #include "utils/builtins.h" @@ -3502,6 +3503,7 @@ inline_function(Oid funcid, Oid result_type, List *args, funcform->prosecdef || funcform->proretset || !heap_attisnull(func_tuple, Anum_pg_proc_proconfig) || + !sepgsql_proc_hint_inlined(func_tuple) || funcform->pronargs != list_length(args)) return NULL; @@ -3970,6 +3972,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) funcform->prosecdef || !funcform->proretset || !heap_attisnull(func_tuple, Anum_pg_proc_proconfig) || + !sepgsql_proc_hint_inlined(func_tuple) || funcform->pronargs != list_length(fexpr->args)) { ReleaseSysCache(func_tuple); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 1d93203..3d3c455 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -21,6 +21,7 @@ #include "optimizer/plancat.h" #include "optimizer/restrictinfo.h" #include "parser/parsetree.h" +#include "security/rowlevel.h" #include "utils/hsearch.h" @@ -91,6 +92,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->has_eclass_joins = false; rel->index_outer_relids = NULL; rel->index_inner_paths = NIL; + rel->rowlvPerms = rowlvSetupPermissions(rte); /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index f110463..a236e3c 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -25,6 +25,7 @@ #include "postgres.h" #include "access/sysattr.h" +#include "catalog/heap.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -660,7 +661,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) tle = makeTargetEntry(expr, attr_num, col->name, - false); + attr_num < 0 ? true : false); qry->targetList = lappend(qry->targetList, tle); rte->modifiedCols = bms_add_member(rte->modifiedCols, @@ -775,6 +776,48 @@ transformInsertRow(ParseState *pstate, List *exprlist, return result; } +static void +transformSelectIntoSystemColumn(ParseState *pstate, Query *qry) +{ + ListCell *l; + uint32 system_attrs = 0; + bool relhasoids + = interpretOidsOption(qry->intoClause->options); + + foreach (l, qry->targetList) + { + Form_pg_attribute attr; + TargetEntry *tle = lfirst(l); + + if (tle->resjunk) + continue; + + attr = SystemAttributeByName(tle->resname, relhasoids); + if (attr && SystemAttributeIsWritable(attr->attnum)) + { + uint32 mask = (1<<(-attr->attnum)); + + /* duplication checks */ + if (system_attrs & mask) + continue; + system_attrs |= mask; + + if (exprType((Node *) tle->expr) != attr->atttypid) + { + tle->expr = + (Expr *) coerce_to_target_type(pstate, + (Node *) tle->expr, + exprType((Node *) tle->expr), + attr->atttypid, + attr->atttypmod, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1); + } + tle->resjunk = true; + } + } +} /* * transformSelectStmt - @@ -879,6 +922,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) if (stmt->intoClause) { qry->intoClause = stmt->intoClause; + transformSelectIntoSystemColumn(pstate, qry); if (stmt->intoClause->colNames) applyColumnNames(qry->targetList, stmt->intoClause->colNames); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index d13b0f0..80a1f4a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -58,6 +58,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/gramparse.h" +#include "security/sepgsql.h" #include "storage/lmgr.h" #include "utils/date.h" #include "utils/datetime.h" @@ -184,7 +185,7 @@ static TypeName *TableFuncTypeName(List *columns); %type stmt schema_stmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt - AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt + AlterObjectSchemaStmt AlterOwnerStmt AlterSecLabelStmt AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt @@ -402,6 +403,10 @@ static TypeName *TableFuncTypeName(List *columns); %type OptTableSpace OptConsTableSpace OptTableSpaceOwner %type opt_check_option +%type OptSecLabel SecLabelItem SecLabelToItem +%type OptTableSecLabel TableSecLabelList +%type TableSecLabelItem + %type xml_attribute_el %type xml_attribute_list xml_attributes %type xml_root_version opt_xml_root_standalone @@ -437,7 +442,7 @@ static TypeName *TableFuncTypeName(List *columns); CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS - CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB + CONTENT_P CONTEXT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB CREATEROLE CREATEUSER CROSS CSV CURRENT_P CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE @@ -608,6 +613,7 @@ stmt : | AlterGroupStmt | AlterObjectSchemaStmt | AlterOwnerStmt + | AlterSecLabelStmt | AlterSeqStmt | AlterTableStmt | AlterRoleSetStmt @@ -1042,7 +1048,7 @@ DropGroupStmt: *****************************************************************************/ CreateSchemaStmt: - CREATE SCHEMA OptSchemaName AUTHORIZATION RoleId OptSchemaEltList + CREATE SCHEMA OptSchemaName AUTHORIZATION RoleId OptSecLabel OptSchemaEltList { CreateSchemaStmt *n = makeNode(CreateSchemaStmt); /* One can omit the schema name or the authorization id. */ @@ -1051,16 +1057,18 @@ CreateSchemaStmt: else n->schemaname = $5; n->authid = $5; - n->schemaElts = $6; + n->secLabel = $6; + n->schemaElts = $7; $$ = (Node *)n; } - | CREATE SCHEMA ColId OptSchemaEltList + | CREATE SCHEMA ColId OptSecLabel OptSchemaEltList { CreateSchemaStmt *n = makeNode(CreateSchemaStmt); /* ...but not both */ n->schemaname = $3; n->authid = NULL; - n->schemaElts = $4; + n->secLabel = $4; + n->schemaElts = $5; $$ = (Node *)n; } ; @@ -2037,7 +2045,7 @@ opt_using: *****************************************************************************/ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' - OptInherit OptWith OnCommitOption OptTableSpace + OptInherit OptWith OnCommitOption OptTableSpace OptTableSecLabel { CreateStmt *n = makeNode(CreateStmt); $4->istemp = $2; @@ -2048,10 +2056,11 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' n->options = $9; n->oncommit = $10; n->tablespacename = $11; + n->secLabel = $12; $$ = (Node *)n; } | CREATE OptTemp TABLE qualified_name OF qualified_name - '(' OptTableElementList ')' OptWith OnCommitOption OptTableSpace + '(' OptTableElementList ')' OptWith OnCommitOption OptTableSpace OptTableSecLabel { /* SQL99 CREATE TABLE OF (cols) seems to be satisfied * by our inheritance capabilities. Let's try it... @@ -2065,6 +2074,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' n->options = $10; n->oncommit = $11; n->tablespacename = $12; + n->secLabel = $13; $$ = (Node *)n; } ; @@ -2114,6 +2124,7 @@ columnDef: ColId Typename ColQualList n->typename = $2; n->constraints = $3; n->is_local = true; + n->secLabel = NULL; $$ = (Node *)n; } ; @@ -2585,12 +2596,13 @@ opt_with_data: *****************************************************************************/ CreateSeqStmt: - CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + CREATE OptTemp SEQUENCE qualified_name OptSeqOptList OptSecLabel { CreateSeqStmt *n = makeNode(CreateSeqStmt); $4->istemp = $2; n->sequence = $4; n->options = $5; + n->secLabel = $6; $$ = (Node *)n; } ; @@ -4893,6 +4905,10 @@ createfunc_opt_item: { $$ = makeDefElem("window", (Node *)makeInteger(TRUE)); } + | SecLabelItem + { + $$ = makeDefElem("security_context", $1); + } | common_func_opt_item { $$ = $1; @@ -5607,6 +5623,101 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId } ; +/***************************************************************************** + * + * ALTER THING name SECURITY CONTEXT TO + * + *****************************************************************************/ + +AlterSecLabelStmt: ALTER DATABASE database_name SecLabelToItem + { + AlterSecLabelStmt *n = makeNode(AlterSecLabelStmt); + n->objectType = OBJECT_DATABASE; + n->object = list_make1(makeString($3)); + n->secLabel = $4; + $$ = (Node *) n; + } + | ALTER SCHEMA name SecLabelToItem + { + AlterSecLabelStmt *n = makeNode(AlterSecLabelStmt); + n->objectType = OBJECT_SCHEMA; + n->object = list_make1(makeString($3)); + n->secLabel = $4; + $$ = (Node *) n; + } + | ALTER TABLE relation_expr SecLabelToItem + { + AlterSecLabelStmt *n = makeNode(AlterSecLabelStmt); + n->objectType = OBJECT_TABLE; + n->relation = $3; + n->secLabel = $4; + $$ = (Node *) n; + } + | ALTER TABLE relation_expr ALTER opt_column ColId SecLabelToItem + { + AlterSecLabelStmt *n = makeNode(AlterSecLabelStmt); + n->objectType = OBJECT_COLUMN; + n->relation = $3; + n->subname = $6; + n->secLabel = $7; + $$ = (Node *) n; + } + | ALTER SEQUENCE relation_expr SecLabelToItem + { + AlterSecLabelStmt *n = makeNode(AlterSecLabelStmt); + n->objectType = OBJECT_SEQUENCE; + n->relation = $3; + n->secLabel = $4; + $$ = (Node *) n; + } + | ALTER FUNCTION function_with_argtypes SecLabelToItem + { + AlterSecLabelStmt *n = makeNode(AlterSecLabelStmt); + n->objectType = OBJECT_FUNCTION; + n->object = $3->funcname; + n->objarg = $3->funcargs; + n->secLabel = $4; + $$ = (Node *) n; + } + | ALTER LARGE_P OBJECT_P Iconst SecLabelToItem + { + AlterSecLabelStmt *n = makeNode(AlterSecLabelStmt); + n->objectType = OBJECT_LARGEOBJECT; + n->object = list_make1(makeInteger($4)); + n->secLabel = $5; + $$ = (Node *) n; + } + ; + +OptTableSecLabel: SECURITY CONTEXT_P '(' TableSecLabelList ')' { $$ = $4; } + | /* EMPTY */ { $$ = NIL; } + ; + +TableSecLabelList: TableSecLabelItem { $$ = list_make1($1); } + | TableSecLabelList ',' TableSecLabelItem { $$ = lappend($1, $3); } + ; + +TableSecLabelItem: Sconst + { $$ = makeDefElem(NULL, (Node *)makeString($1)); } + | ColId '=' Sconst + { $$ = makeDefElem($1, (Node *)makeString($3)); } + ; + +OptSecLabel: SecLabelItem { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +SecLabelItem: SECURITY CONTEXT_P '(' Sconst ')' + { + $$ = (Node *) makeString($4); + } + ; + +SecLabelToItem: SECURITY CONTEXT_P TO Sconst + { + $$ = (Node *) makeString($4); + } + ; /***************************************************************************** * @@ -6049,6 +6160,10 @@ createdb_opt_item: { $$ = makeDefElem("owner", NULL); } + | SecLabelItem + { + $$ = makeDefElem("security_context", $1); + } ; /* @@ -10175,6 +10290,7 @@ unreserved_keyword: | CONNECTION | CONSTRAINTS | CONTENT_P + | CONTEXT_P | CONTINUE_P | CONVERSION_P | COPY diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 08b8edb..97aa7aa 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "catalog/heap.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "funcapi.h" @@ -361,16 +362,33 @@ transformAssignedExpr(ParseState *pstate, Oid attrtype; /* type of target column */ int32 attrtypmod; Relation rd = pstate->p_target_relation; + bool relhasoids = RelationGetForm(rd)->relhasoids; Assert(rd != NULL); - if (attrno <= 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot assign to system column \"%s\"", - colname), - parser_errposition(pstate, location))); - attrtype = attnumTypeId(rd, attrno); - attrtypmod = rd->rd_att->attrs[attrno - 1]->atttypmod; + if (attrno > 0) + { + attrtype = attnumTypeId(rd, attrno); + attrtypmod = rd->rd_att->attrs[attrno - 1]->atttypmod; + } + else + { + Form_pg_attribute attForm + = SystemAttributeDefinition(attrno, relhasoids); + if (attForm && SystemAttributeIsWritable(attrno)) + { + attrtype = attForm->atttypid; + attrtypmod = attForm->atttypmod; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot assign to system column \"%s\"", + colname), + parser_errposition(pstate, location))); + return NULL; /* compiler kindness */ + } + } /* * If the expression is a DEFAULT placeholder, insert the attribute's @@ -515,6 +533,9 @@ updateTargetListEntry(ParseState *pstate, */ tle->resno = (AttrNumber) attrno; tle->resname = colname; + + if (SystemAttributeIsWritable(attrno)) + tle->resjunk = true; } @@ -789,6 +810,7 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) Bitmapset *wholecols = NULL; Bitmapset *partialcols = NULL; ListCell *tl; + uint32 system_attrs = 0UL; foreach(tl, cols) { @@ -797,14 +819,37 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) int attrno; /* Lookup column name, ereport on failure */ - attrno = attnameAttNum(pstate->p_target_relation, name, false); + attrno = attnameAttNum(pstate->p_target_relation, name, true); if (attrno == InvalidAttrNumber) + { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", name, RelationGetRelationName(pstate->p_target_relation)), parser_errposition(pstate, col->location))); + } + else if (attrno < 0) + { + if (SystemAttributeIsWritable(attrno)) + { + uint32 mask = (1<<(-attrno)); + + if ((system_attrs & mask) != 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column \"%s\" specified more than once", name), + parser_errposition(pstate, col->location))); + system_attrs |= mask; + *attrnos = lappend_int(*attrnos, attrno); + continue; + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("column \"%s\" of relation \"%s\" is system column", + name, RelationGetRelationName(pstate->p_target_relation)), + parser_errposition(pstate, col->location))); + } /* * Check for duplicates, but only of whole columns --- we allow diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 1a9e387..c5ea995 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -49,6 +49,7 @@ #include "parser/parse_type.h" #include "parser/parse_utilcmd.h" #include "rewrite/rewriteManip.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -565,6 +566,8 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(relation)); + /* SELinux checks */ + sepgsql_relation_copy_definition(RelationGetRelid(relation)); tupleDesc = RelationGetDescr(relation); constr = tupleDesc->constr; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 3e1a056..48d0642 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2004,7 +2004,7 @@ do_autovacuum(void) object.classId = RelationRelationId; object.objectId = relid; object.objectSubId = 0; - performDeletion(&object, DROP_CASCADE); + performDeletionNoPerms(&object, DROP_CASCADE); } else { diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index d63214b..e88b469 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -108,6 +108,7 @@ #include "postmaster/pgarch.h" #include "postmaster/postmaster.h" #include "postmaster/syslogger.h" +#include "security/sepgsql.h" #include "storage/fd.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" @@ -209,7 +210,8 @@ static pid_t StartupPID = 0, AutoVacPID = 0, PgArchPID = 0, PgStatPID = 0, - SysLoggerPID = 0; + SysLoggerPID = 0, + sepgsqlReceiverPID = 0; /* Startup/shutdown state */ #define NoShutdown 0 @@ -445,6 +447,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartupDataBase() StartChildProcess(StartupProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) +#define StartSELinuxReceiver() StartChildProcess(SelinuxReceiverProcess) /* Macros to check exit status of a child process */ #define EXIT_STATUS_0(st) ((st) == 0) @@ -1436,6 +1439,11 @@ ServerLoop(void) if (PgStatPID == 0 && pmState == PM_RUN) PgStatPID = pgstat_start(); + /* if we have lost the selinux netlink receiver, try to start */ + if (sepgsqlReceiverPID == 0 && pmState == PM_RUN && + sepgsqlReceiverStart()) + sepgsqlReceiverPID = StartSELinuxReceiver(); + /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) { @@ -2055,6 +2063,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(SysLoggerPID, SIGHUP); if (PgStatPID != 0) signal_child(PgStatPID, SIGHUP); + if (sepgsqlReceiverPID != 0) + signal_child(sepgsqlReceiverPID, SIGHUP); /* Reload authentication config files too */ if (!load_hba()) @@ -2115,6 +2125,9 @@ pmdie(SIGNAL_ARGS) /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); + /* and the selinux netlink receiver too */ + if (sepgsqlReceiverPID != 0) + signal_child(sepgsqlReceiverPID, SIGTERM); pmState = PM_WAIT_BACKUP; } @@ -2162,6 +2175,9 @@ pmdie(SIGNAL_ARGS) /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); + /* and the selinux netlink receiver too */ + if (sepgsqlReceiverPID != 0) + signal_child(sepgsqlReceiverPID, SIGTERM); pmState = PM_WAIT_BACKENDS; } @@ -2195,6 +2211,8 @@ pmdie(SIGNAL_ARGS) signal_child(PgArchPID, SIGQUIT); if (PgStatPID != 0) signal_child(PgStatPID, SIGQUIT); + if (sepgsqlReceiverPID != 0) + signal_child(sepgsqlReceiverPID, SIGQUIT); ExitPostmaster(0); break; } @@ -2457,6 +2475,16 @@ reaper(SIGNAL_ARGS) continue; } + /* Was it the selinux netlink receiver process? */ + if (pid == sepgsqlReceiverPID) + { + sepgsqlReceiverPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + LogChildExit(LOG, _("SELinux netlink receiver process"), + pid, exitstatus); + continue; + } + /* * Else do standard backend child cleanup. */ @@ -2648,6 +2676,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the selinux netlink receiver too */ + if (pid == sepgsqlReceiverPID) + sepgsqlReceiverPID = 0; + else if (sepgsqlReceiverPID != 0 && !FatalError) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) sepgsqlReceiverPID))); + signal_child(sepgsqlReceiverPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -2780,7 +2820,8 @@ PostmasterStateMachine(void) StartupPID == 0 && (BgWriterPID == 0 || !FatalError) && WalWriterPID == 0 && - AutoVacPID == 0) + AutoVacPID == 0 && + sepgsqlReceiverPID == 0) { if (FatalError) { @@ -4323,6 +4364,12 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork WAL writer process: %m"))); break; +#ifdef HAVE_SELINUX + case SelinuxReceiverProcess: + ereport(LOG, + (errmsg("could not fork selinux receiver process: %m"))); + break; +#endif default: ereport(LOG, (errmsg("could not fork process: %m"))); diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 0b4f279..5fe6fd0 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -27,6 +27,7 @@ #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteManip.h" #include "rewrite/rewriteSupport.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/inval.h" @@ -266,6 +267,9 @@ DefineQueryRewrite(char *rulename, aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(event_relation)); + /* SELinux checks */ + sepgsql_rule_create(event_relid, rulename); + /* * No rule actions that modify OLD or NEW */ diff --git a/src/backend/rewrite/rewriteRemove.c b/src/backend/rewrite/rewriteRemove.c index a65b020..b967838 100644 --- a/src/backend/rewrite/rewriteRemove.c +++ b/src/backend/rewrite/rewriteRemove.c @@ -22,6 +22,7 @@ #include "catalog/pg_rewrite.h" #include "miscadmin.h" #include "rewrite/rewriteRemove.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/inval.h" @@ -78,6 +79,9 @@ RemoveRewriteRule(Oid owningRel, const char *ruleName, DropBehavior behavior, aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, get_rel_name(eventRelationOid)); + /* SELinux checks */ + sepgsql_rule_drop(eventRelationOid, ruleName); + /* * Do the deletion */ diff --git a/src/backend/security/Makefile b/src/backend/security/Makefile new file mode 100644 index 0000000..23e51d6 --- /dev/null +++ b/src/backend/security/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for the enhanced security subsystem +# + +subdir = src/backend/security +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +SUBDIRS = sepgsql + +OBJS = rowlevel.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/security/rowlevel.c b/src/backend/security/rowlevel.c new file mode 100644 index 0000000..e08d4cc --- /dev/null +++ b/src/backend/security/rowlevel.c @@ -0,0 +1,121 @@ +/* + * src/backend/security/common.c + * common facilities for row-level access controls both of DAC and MAC + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "catalog/pg_security.h" +#include "security/rowlevel.h" +#include "security/sepgsql.h" +#include "storage/bufmgr.h" +#include "storage/bufpage.h" +#include "utils/rel.h" +#include "utils/tqual.h" + +/* + * rowlvGetPerformingMode + * rowlvSetPerformingMode + * enables to control the behavior of row-level features + * when violated tuples are detected. + * The default is ROWLV_FILTER_MODE which filters out + * violated tuples from result set, ROWLV_ABORT_MODE + * raises an error and ROWLV_BYPASS_MODE do nothing. + */ +static int rowlv_mode = ROWLV_FILTER_MODE; + +int rowlvGetPerformingMode(void) +{ + return rowlv_mode; +} + +int rowlvSetPerformingMode(int new_mode) +{ + int old_mode = new_mode; + + rowlv_mode = new_mode; + + return old_mode; +} + +/* + * rowlvSetupPermissions + * setups permissions for row-level access controls. + */ +uint32 +rowlvSetupPermissions(RangeTblEntry *rte) +{ + return sepgsqlSetupTuplePerms(rte); +} + +/* + * rowlvExecScan + * a hook to filter out invisible/untouchable tuples. + */ +static bool +rowlvExecScan(Scan *scan, Relation rel, TupleTableSlot *slot, bool abort) +{ + HeapTuple tuple; + uint32 perms = scan->rowlvPerms; + + if (!perms) + return true; + + tuple = ExecMaterializeSlot(slot); + + return sepgsqlExecScan(rel, tuple, perms, abort); +} + +bool +rowlvExecScanFilter(Scan *scan, Relation rel, TupleTableSlot *slot) +{ + if (!rel || !scan->rowlvPerms || rowlv_mode != ROWLV_FILTER_MODE) + return true; + + return rowlvExecScan(scan, rel, slot, false); +} + +void +rowlvExecScanAbort(Scan *scan, Relation rel, TupleTableSlot *slot) +{ + if (!rel || !scan->rowlvPerms || rowlv_mode != ROWLV_ABORT_MODE) + return; + + rowlvExecScan(scan, rel, slot, true); +} + +/* + * rowlvCopyToTuple + * checks permission on fetched tuple + */ +bool +rowlvCopyToTuple(Relation rel, HeapTuple tuple) +{ + if (!sepgsqlExecScan(rel, tuple, SEPG_DB_TUPLE__SELECT, false)) + return false; + + return true; +} + +/* + * rowlvHeapTupleInsert + * assign default security attribute, and check permission + * if necessary. + */ +void +rowlvHeapTupleInsert(Relation rel, HeapTuple newtup, bool internal) +{ + sepgsqlHeapTupleInsert(rel, newtup, internal); +} + +/* + * rowlvHeapTupleUpdate + * check permission to change security attribute, if necesary + */ +void +rowlvHeapTupleUpdate(Relation rel, ItemPointer otid, HeapTuple newtup) +{ + sepgsqlHeapTupleUpdate(rel, otid, newtup); +} diff --git a/src/backend/security/sepgsql/Makefile b/src/backend/security/sepgsql/Makefile new file mode 100644 index 0000000..2417aae --- /dev/null +++ b/src/backend/security/sepgsql/Makefile @@ -0,0 +1,15 @@ +# +# Makefile +# Makefile for utils/sepgsql : SE-PostgreSQL +# + +subdir = src/backend/security/sepgsql +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = misc.o +ifeq ($(enable_selinux), yes) +OBJS += selinux.o checker.o bridge.o label.o +endif + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/security/sepgsql/avc.c b/src/backend/security/sepgsql/avc.c new file mode 100644 index 0000000..8a88bcb --- /dev/null +++ b/src/backend/security/sepgsql/avc.c @@ -0,0 +1,881 @@ +/* + * src/backend/security/sepgsql/avc.c + * SE-PostgreSQL userspace access vector cache + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "access/hash.h" +#include "catalog/pg_security.h" +#include "libpq/pqsignal.h" +#include "miscadmin.h" +#include "postmaster/postmaster.h" +#include "security/sepgsql.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "utils/memutils.h" +#include +#include +#include + +/* + * AVC: userspace access vector cache + * + * SE-PostgreSQL asks in-kernel SELinux to make its decision whether + * the required accesses should be allowed, or not, based on the unified + * security policy. It needs a system call invocation to communicate + * a kernel feature, such as SELinux, but it is a heavy task in most cases + * due to the context switching. + * + * The userspace avc enables to minimize the number of system call + * invocations, using a chache mechanim for the certain pair of security + * contexts and object classes (it means the kind of actions). + * It enables to hold recently fetched results from the in-kernel SELinux, + * and make a decision without context switching, if the cache hit. + * + * When the state of security policy is changed, the cached results + * shall to be invalidated. The state monitoring process launched by + * postmaster can receives the notification messages from the kernel + * space, and invalidate the current version of avc. + */ +static MemoryContext AvcMemCtx = NULL; + +#define AVC_HASH_NUM_SLOTS 256 +#define AVC_HASH_NUM_NODES 180 + +#define AVC_DATUM_NSID_SLOTS 19 +typedef struct +{ + uint32 hash_key; + + security_class_t tclass; + sepgsql_sid_t tsid; + sepgsql_sid_t nsid[AVC_DATUM_NSID_SLOTS]; + + access_vector_t allowed; + access_vector_t decided; + access_vector_t auditallow; + access_vector_t auditdeny; + + bool hot_cache; + bool permissive; + + char ncontext[1]; +} avc_datum; + +typedef struct avc_page +{ + struct avc_page *next; + + security_context_t scontext; + + List *slot[AVC_HASH_NUM_SLOTS]; + + uint32 avc_count; + uint32 lru_hint; +} avc_page; + +static avc_page *current_page = NULL; + +static int avc_version; + +/* + * selinux_state + * + * It is deployed on the shared memory region, to show the system + * state of SELinux and its security policy. + * + * The selinux_state->version should be checked prior to avc accesses. + * If it does not match with the local avc_version, it means that + * system security policy was reloaded or system state (enforcing + * or permissive) was changed. + * + * The state monitoring worker process receives messages from the + * kernel using libselinux, and it updates the selinux_state. + */ +struct +{ + int version; + + bool enforcing; + +} *selinux_state = NULL; + +Size +sepgsqlShmemSize(void) +{ + if (!sepgsqlIsEnabled()) + return 0; + + return sizeof(*selinux_state); +} + +/* + * sepgsql_shmem_init + * attaches shared memory segment. + */ +static void +sepgsqlShmemInit(void) +{ + bool found; + + selinux_state = ShmemInitStruct("SELinux policy state", + sepgsqlShmemSize(), &found); + if (!found) + { + LWLockAcquire(SepgsqlAvcLock, LW_EXCLUSIVE); + + selinux_state->version = 0; + selinux_state->enforcing = (security_getenforce() > 0); + + LWLockRelease(SepgsqlAvcLock); + } +} + +/* + * sepgsqlAvcReset + * + * It invalidate access vector cache. It has to be called on errors, + * because avc entries for newly created context is uncertain whether + * it is still valid, or not. + * If error happens before avc initialization, we simply skip it. + */ +void +sepgsqlAvcReset(void) +{ + if (!sepgsqlIsEnabled() || !AvcMemCtx) + return; + + MemoryContextReset(AvcMemCtx); + + current_page = NULL; + + sepgsqlAvcSwitchClient(sepgsqlGetClientLabel()); +} + +/* + * sepgsqlAvcCheckValid + * + * It checks whether the current AVC pages are valid, or not. + * If state monitoring process already received an invalidation + * message from the kernel, it clears current AVC pages and + * returns false. + */ +static bool +sepgsqlAvcCheckValid(void) +{ + bool result = true; + + LWLockAcquire(SepgsqlAvcLock, LW_SHARED); + if (avc_version != selinux_state->version) + { + /* reset invalid avc pages, and makes an empty one */ + MemoryContextReset(AvcMemCtx); + + current_page = NULL; + + sepgsqlAvcSwitchClient(sepgsqlGetClientLabel()); + + /* copy current version to local */ + avc_version = selinux_state->version; + + result = false; + } + LWLockRelease(SepgsqlAvcLock); + + return result; +} + +/* + * sepgsqlAvcInitialize + * + * It allocates a memory context for userspace AVC, + * map shared memory segment, and initialize avc_page + * for the current client's privilege. + * + * If the current backend is not associated with a certain + * client process, it switches to permissive mode to avoid + * to prevent any internal processes. + */ +void +sepgsqlAvcInitialize(void) +{ + if (!sepgsqlIsEnabled()) + return; + + /* + * local memory context + */ + AvcMemCtx = AllocSetContextCreate(TopMemoryContext, + "SE-PostgreSQL userspace avc", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + sepgsqlShmemInit(); + + /* + * Switch to local permissive mode + */ + if (!MyProcPort) + sepgsqlSetEnforce(0); + + /* + * selinux_state->version is never negative value, + * so this call always reset local avc. + */ + avc_version = -1; + sepgsqlAvcCheckValid(); +} + +/* + * sepgsqlGetEnforce + * sepgsqlSetEnforce + * + * SELinux has two working mode called Enforcing/Permissive. + * In enforcing mode, it checks security policy and actually + * applies its access controls. In permissive mode, it also + * checks security policy, but does not apply any access + * controls. It is used to collect access denied logs to + * debug security policy. + * + * sepgsqlGetEnforce() returns the current working mode, and + * sepgsqlSetEnforce() switches the current working mode + * temporary. When we switches the mode, any errors have to + * be acquired, and it should be restored correctly. + */ +static int local_enforce = -1; /* undefined */ + +bool +sepgsqlGetEnforce(void) +{ + bool rc; + + if (local_enforce < 0) + { + LWLockAcquire(SepgsqlAvcLock, LW_SHARED); + rc = selinux_state->enforcing; + LWLockRelease(SepgsqlAvcLock); + + return rc; + } + + return (local_enforce > 0 ? true : false); +} + +int +sepgsqlSetEnforce(int new_mode) +{ + int old_mode = local_enforce; + + local_enforce = new_mode; + + return old_mode; +} + +/* + * sepgsqlAvcAudit + * + * It write out audit message, when auditdeny or auditallow + * matches the required permission bits. + * If external module support sepgsqlAvcAuditHook, it allows + * to write audit logs to external log manager, such as system + * auditd. + */ + +PGDLLIMPORT sepgsqlAvcAuditHook_t sepgsqlAvcAuditHook = NULL; + +static void +sepgsqlAvcAudit(bool denied, char *scontext, char *tcontext, + uint16 tclass, uint32 audited, const char *audit_name) +{ + StringInfoData buf; + uint32 mask; + const char *tclass_name; + + /* translate to human readable form */ + scontext = sepgsqlTransSecLabelOut(scontext); + tcontext = sepgsqlTransSecLabelOut(tcontext); + + /* permissions in text representation */ + initStringInfo(&buf); + appendStringInfo(&buf, "{"); + for (mask = 1; audited != 0; mask <<= 1) + { + if (audited & mask) + appendStringInfo(&buf, " %s", sepgsqlGetPermString(tclass, mask)); + + audited &= ~mask; + } + appendStringInfo(&buf, " }"); + + tclass_name = sepgsqlGetClassString(tclass); + + /* call external audit module, if loaded */ + if (sepgsqlAvcAuditHook) + (*sepgsqlAvcAuditHook) (denied, scontext, tcontext, + tclass_name, buf.data, audit_name); + else + { + appendStringInfo(&buf, " scontext=%s tcontext=%s tclass=%s", + scontext, tcontext, tclass_name); + if (audit_name) + appendStringInfo(&buf, " name=%s", audit_name); + + ereport(LOG, + (errcode(ERRCODE_SELINUX_AUDIT), + errmsg("SELinux: %s %s", + denied ? "denied" : "granted", buf.data))); + } +} + +/* + * sepgsqlAvcReclaim + * + * It wipes recently unused AVC entries, when the number of entries + * reaches AVC_HASH_NUM_NODES.. + */ +static void +sepgsqlAvcReclaim(avc_page *page) +{ + ListCell *l; + avc_datum *cache; + + while (page->avc_count > AVC_HASH_NUM_NODES) + { + foreach (l, page->slot[page->lru_hint]) + { + cache = lfirst(l); + + if (cache->hot_cache) + cache->hot_cache = false; + else + { + list_delete_ptr(page->slot[page->lru_hint], cache); + pfree(cache); + page->avc_count--; + } + } + page->lru_hint = (page->lru_hint + 1) % AVC_HASH_NUM_SLOTS; + } +} + +/* + * sepgsqlAvcMakeEntry + * + * It makes a new AVC entry and insert it on the avc_page. + * If is hold more than AVC_HASH_NUM_NODES entries, recently unused + * avc_datum shall be reclaimed. + */ +#define avc_hash_key(trelid,tsecid,tclass) \ + (hash_uint32((trelid) ^ (tsecid) ^ ((tclass) << 3))) + +static avc_datum * +sepgsqlAvcMakeEntry(avc_page *page, sepgsql_sid_t tsid, uint16 tclass) +{ + security_context_t scontext, tcontext, ncontext; + security_class_t tclass_ex; + MemoryContext oldctx; + struct av_decision avd; + avc_datum *cache; + uint32 hash_key, index; + + hash_key = avc_hash_key(tsid.relid, tsid.secid, tclass); + index = hash_key % AVC_HASH_NUM_SLOTS; + + scontext = page->scontext; + tcontext = securityRawSecLabelOut(tsid.relid, tsid.secid); + + /* + * Compute SELinux permission + */ + tclass_ex = sepgsqlTransToExternalClass(tclass); + if (tclass_ex > 0) + { + if (security_compute_av_flags_raw(scontext, tcontext, + tclass_ex, 0, &avd) < 0) + ereport(ERROR, + (errcode(ERRCODE_SELINUX_ERROR), + errmsg("SELinux: unable to compute av_decision: " + "scontext=%s tcontext=%s tclass=%s", + scontext, tcontext, + sepgsqlGetClassString(tclass)))); + sepgsqlTransToInternalPerms(tclass, &avd); + } + else + { + /* fill it up as undefined class */ + avd.allowed = (security_deny_unknown() ? 0 : ~0UL); + avd.decided = ~0UL; + avd.auditallow = 0UL; + avd.auditdeny = ~0UL; + avd.flags = 0; + } + + /* + * Compute New security context + */ + if (security_compute_create_raw(scontext, tcontext, + tclass_ex, &ncontext) < 0) + { + ereport(ERROR, + (errcode(ERRCODE_SELINUX_ERROR), + errmsg("SELinux: unable to compute new context: " + "scontext=%s tcontext=%s tclass=%s", + scontext, tcontext, sepgsqlGetClassString(tclass)))); + } + + /* + * Copy them to avc_datum + */ + oldctx = MemoryContextSwitchTo(AvcMemCtx); + PG_TRY(); + { + cache = palloc0(sizeof(avc_datum) + strlen(ncontext)); + } + PG_CATCH(); + { + freecon(ncontext); + PG_RE_THROW(); + } + PG_END_TRY(); + + cache->hash_key = hash_key; + cache->tclass = tclass; + cache->tsid.relid = tsid.relid; + cache->tsid.secid = tsid.secid; + /* cache->nsid shall be set later */ + + cache->allowed = avd.allowed; + cache->decided = avd.decided; + cache->auditallow = avd.auditallow; + cache->auditdeny = avd.auditdeny; + + cache->hot_cache = true; + if (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) + cache->permissive = true; + strcpy(cache->ncontext, ncontext); + freecon(ncontext); + + sepgsqlAvcReclaim(page); + + page->slot[index] = lcons(cache, page->slot[index]); + page->avc_count++; + + MemoryContextSwitchTo(oldctx); + + return cache; +} + +/* + * sepgsqlAvcLookup + * + * It lookups required AVC entry. + */ +static avc_datum * +sepgsqlAvcLookup(avc_page *page, sepgsql_sid_t tsid, uint16 tclass) +{ + avc_datum *cache = NULL; + uint32 hash_key, index; + ListCell *l; + + hash_key = avc_hash_key(tsid.relid, tsid.secid, tclass); + index = hash_key % AVC_HASH_NUM_SLOTS; + + foreach (l, page->slot[index]) + { + cache = lfirst(l); + if (cache->hash_key == hash_key + && cache->tclass == tclass + && cache->tsid.relid == tsid.relid + && cache->tsid.secid == tsid.secid) + { + cache->hot_cache = true; + return cache; + } + } + return NULL; +} + +/* + * sepgsqlAvcSwitchClientLabel() + * + * It switches the current avc_page. + * An avc_page is a set of cached access control decisions associated + * with a certain privilege of the client. This structure enables to + * lookup required avc_datum without any comparison to the subject + * label. + */ +void +sepgsqlAvcSwitchClient(const char *scontext) +{ + MemoryContext oldctx; + avc_page *new_page; + int i; + + if (current_page) + { + new_page = current_page; + do { + if (strcmp(new_page->scontext, scontext) == 0) + { + current_page = new_page; + return; + } + new_page = new_page->next; + } while (new_page != current_page); + } + + /* Not found, create a new avc_page */ + oldctx = MemoryContextSwitchTo(AvcMemCtx); + new_page = palloc0(sizeof(avc_page)); + new_page->scontext = pstrdup(scontext); + MemoryContextSwitchTo(oldctx); + + for (i=0; i < AVC_HASH_NUM_SLOTS; i++) + new_page->slot[i] = NIL; + + if (!current_page) + new_page->next = new_page; + else + { + new_page->next = current_page->next; + current_page->next = new_page; + } + + current_page = new_page; +} + +/* + * sepgsqlClientHasPerms + * + * It checks client's privileges on the given object using avc. + */ +bool +sepgsqlClientHasPerms(sepgsql_sid_t tsid, + uint16 tclass, uint32 required, + const char *audit_name, bool abort) +{ + avc_datum *cache; + uint32 denied, audited; + bool result = true; + + Assert(required != 0); + + do { + cache = sepgsqlAvcLookup(current_page, tsid, tclass); + if (!cache) + cache = sepgsqlAvcMakeEntry(current_page, tsid, tclass); + } while (!sepgsqlAvcCheckValid()); + + denied = required & ~cache->allowed; + audited = denied ? (denied & cache->auditdeny) + : (required & cache->auditallow); + if (audited) + { + sepgsqlAvcAudit(!!denied, + current_page->scontext, + securityRawSecLabelOut(tsid.relid, tsid.secid), + cache->tclass, audited, audit_name); + } + + if (denied) + { + if (!sepgsqlGetEnforce() || cache->permissive) + cache->allowed |= required; /* prevent flood of audit log */ + else + { + if (abort) + ereport(ERROR, + (errcode(ERRCODE_SELINUX_ERROR), + errmsg("SELinux: security policy violation"))); + result = false; + } + } + + return result; +} + +/* + * sepgsqlClientCreateSecid + * sepgsqlClientCreateLabel + */ +sepgsql_sid_t +sepgsqlClientCreateSecid(sepgsql_sid_t tsid, uint16 tclass, Oid nrelid) +{ + sepgsql_sid_t nsid; + avc_datum *cache; + int index; + + do { + cache = sepgsqlAvcLookup(current_page, tsid, tclass); + if (!cache) + cache = sepgsqlAvcMakeEntry(current_page, tsid, tclass); + + index = (nrelid % AVC_DATUM_NSID_SLOTS); + if (cache->nsid[index].relid != nrelid) + { + cache->nsid[index].secid + = securityRawSecLabelIn(nrelid, cache->ncontext); + cache->nsid[index].relid = nrelid; + } + nsid = cache->nsid[index]; + } while (!sepgsqlAvcCheckValid()); + + return nsid; +} + +security_context_t +sepgsqlClientCreateLabel(sepgsql_sid_t tsid, uint16 tclass) +{ + avc_datum *cache; + + do { + cache = sepgsqlAvcLookup(current_page, tsid, tclass); + if (!cache) + cache = sepgsqlAvcMakeEntry(current_page, tsid, tclass); + } while (!sepgsqlAvcCheckValid()); + + return cache->ncontext; +} + +/* + * sepgsqlComputePerms + * sepgsqlComputeCreate + * + * The following two functions make a query to in-kernel SELinux + * without userspace caches, due to some reasons. + * The AVC can cover most of cases, but some of corner cases are + * not suitable for AVC structure, so we need uncached interfaces. + * For example, AVC is unavailable when we tries to load a shared + * library module, because security context of the library does not + * have its security identifier, so we cannot put it on AVC. + */ +bool +sepgsqlComputePerms(char *scontext, char *tcontext, + uint16 tclass_in, uint32 required, + const char *audit_name, bool abort) +{ + access_vector_t denied, audited; + security_class_t tclass_ex; + struct av_decision avd; + + Assert(required != 0); + + tclass_ex = sepgsqlTransToExternalClass(tclass_in); + if (tclass_ex > 0) + { + /* + * security_compute_av_flags_raw() is a SELinux's API that + * returns its access control decision based on the security + * policy, to the given combination of user's privilege + * (scontext; security label of the client process), + * target's attribute (tcontext; security label of the + * object) and type of actions (tclass; object classes). + * + * The returned avd.allowed is a bitmap of allowed actions. + */ + if (security_compute_av_flags_raw(scontext, tcontext, + tclass_ex, 0, &avd) < 0) + ereport(ERROR, + (errcode(ERRCODE_SELINUX_ERROR), + errmsg("SELinux: could not compute av_decision: " + "scontext=%s tcontext=%s tclass=%s", + scontext, tcontext, + sepgsqlGetClassString(tclass_in)))); + sepgsqlTransToInternalPerms(tclass_in, &avd); + } + else + { + /* + * If security policy does not support database related + * permissions, it fulls up permission bits by dummy + * data. + * If security_deny_unknown() returns positive value, + * undefined permissions should not be allowed. + * Otherwise, it shall be allowed. + */ + avd.allowed = (security_deny_unknown() > 0 ? 0 : ~0UL); + avd.decided = ~0UL; + avd.auditallow = 0UL; + avd.auditdeny = ~0UL; + avd.flags = 0; + } + + denied = required & ~avd.allowed; + audited = denied ? (denied & avd.auditdeny) + : (required & avd.auditallow); + if (audited) + { + /* + * If security policy requires to generate an audit log + * record for the given request, it should be logged. + */ + sepgsqlAvcAudit(!!denied, scontext, tcontext, + tclass_in, audited, audit_name); + } + + /* + * If any required permissions are not allowed, and + * SE-PgSQL performs in enforcing mode, and the given + * combination of subject, object and action does not + * have special flag to be handled as permission, + * SE-PgSQL returns false or raises an error. + * Otherwise, it returns true that means required + * actions are allowed. + */ + if (!denied || /* no policy violation */ + !sepgsqlGetEnforce() || /* permissive mode */ + (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) != 0) /* permissive domain */ + return true; + + if (abort) + ereport(ERROR, + (errcode(ERRCODE_SELINUX_ERROR), + errmsg("SELinux: security policy violation"))); + + return false; +} + +char * +sepgsqlComputeCreate(char *scontext, char *tcontext, uint16 tclass_in) +{ + security_context_t ncontext, result; + security_class_t tclass_ex; + + tclass_ex = sepgsqlTransToExternalClass(tclass_in); + /* + * security_compute_create_raw() is a SELinux's API that + * returns a default security context to be assigned on + * a new object (categorized by object class) when a client + * labeled as scontext tries to create a new one under the + * parent object labeled as tcontext. + */ + if (security_compute_create_raw(scontext, tcontext, + tclass_ex, &ncontext) < 0) + ereport(ERROR, + (errcode(ERRCODE_SELINUX_ERROR), + errmsg("SELinux: could not compute a new context " + "scontext=%s tcontext=%s tclass=%s", + scontext, tcontext, sepgsqlGetClassString(tclass_in)))); + PG_TRY(); + { + result = pstrdup(ncontext); + } + PG_CATCH(); + { + freecon(ncontext); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(ncontext); + + return result; +} + +/* + * SELinux state monitoring process + * + * This process is forked from postmaster to monitor the state of SELinux. + * SELinux can make a notifier message to userspace object manager via + * netlink socket. When it receives the message, it updates selinux_state + * structure assigned on shared memory region to make any instance reset + * its AVC soon. + */ +static int +sepgsql_cb_log(int type, const char *fmt, ...) +{ + char *c, buffer[1024]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + c = strrchr(buffer, '\n'); + if (c) + *c = '\0'; + + ereport(LOG, + (errcode(ERRCODE_SELINUX_INFO), + errmsg("%s", buffer))); + + return 0; +} + +static int +sepgsql_cb_setenforce(int enforce) +{ + /* switch enforcing/permissive */ + LWLockAcquire(SepgsqlAvcLock, LW_EXCLUSIVE); + selinux_state->enforcing = (enforce ? true : false); + selinux_state->version++; + LWLockRelease(SepgsqlAvcLock); + + return 0; +} + +static int +sepgsql_cb_policyload(int seqno) +{ + /* invalidate local avc */ + LWLockAcquire(SepgsqlAvcLock, LW_EXCLUSIVE); + selinux_state->version++; + LWLockRelease(SepgsqlAvcLock); + + return 0; +} + +void +sepgsqlReceiverMain(void) +{ + union selinux_callback cb; + + Assert(sepgsqlIsEnabled()); + +#ifdef HAVE_SETSID + if (setsid() < 0) + elog(FATAL, "setsid() failed: %m"); +#endif + + /* + * setup the signal handler + */ + pqinitmask(); + pqsignal(SIGHUP, SIG_IGN); + pqsignal(SIGINT, SIG_IGN); + pqsignal(SIGTERM, exit); + pqsignal(SIGQUIT, exit); + pqsignal(SIGUSR1, SIG_IGN); + pqsignal(SIGUSR2, SIG_IGN); + pqsignal(SIGCHLD, SIG_DFL); + PG_SETMASK(&UnBlockSig); + + /* + * map shared memory segment + */ + sepgsqlShmemInit(); + + ereport(LOG, + (errcode(ERRCODE_SELINUX_INFO), + errmsg("SELinux: security policy monitor (pid=%u)", getpid()))); + /* + * setup callback functions from avc_netlink_loop() + */ + cb.func_log = sepgsql_cb_log; + selinux_set_callback(SELINUX_CB_LOG, cb); + cb.func_setenforce = sepgsql_cb_setenforce; + selinux_set_callback(SELINUX_CB_SETENFORCE, cb); + cb.func_policyload = sepgsql_cb_policyload; + selinux_set_callback(SELINUX_CB_POLICYLOAD, cb); + + /* + * open netlink socket and wait for messages + */ + avc_netlink_open(1); + + avc_netlink_loop(); + + exit(0); +} diff --git a/src/backend/security/sepgsql/bridge.c b/src/backend/security/sepgsql/bridge.c new file mode 100644 index 0000000..3a3630c --- /dev/null +++ b/src/backend/security/sepgsql/bridge.c @@ -0,0 +1,2920 @@ +/* + * src/backend/security/sepgsql/bridge.c + * + * New style security hooks for SE-PostgreSQL + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "access/sysattr.h" +#include "catalog/heap.h" +#include "catalog/indexing.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_database.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject_metadata.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_security.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_type.h" +#include "catalog/pg_user_mapping.h" +#include "commands/dbcommands.h" +#include "miscadmin.h" +#include "security/sepgsql.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/tqual.h" + +#include +#include + +/* ------------------------------------------------------------ * + * Common Helper Routines + * ------------------------------------------------------------ */ +static bool sepgsql_database_common(Oid datOid, uint32 required, bool abort); +static bool sepgsql_schema_common(Oid nspOid, uint32 required, bool abort); +static bool sepgsql_attribute_common(Oid relOid, AttrNumber attnum, + uint32 required, bool abort); +static bool sepgsql_relation_common(Oid relOid, uint32 required, bool abort); +static bool sepgsql_proc_common(Oid procOid, uint32 required, bool abort); +static bool sepgsql_fdw_common(Oid fdwOid, uint32 required, bool abort); +static bool sepgsql_foreign_server_common(Oid fsrvOid, uint32 required, bool abort); +static bool sepgsql_language_common(Oid langOid, uint32 required, bool abort); +static bool sepgsql_operator_common(Oid oprOid, uint32 required, bool abort); + +/* ------------------------------------------------------------ * + * + * Pg_database related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_database_common(Oid datOid, uint32 required, bool abort) +{ + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + const char *auname; + bool rc; + + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(datOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for database: %u", datOid); + + auname = NameStr(((Form_pg_database) GETSTRUCT(tuple))->datname); + + sid = sepgsqlGetTupleSecid(DatabaseRelationId, tuple, &tclass); + + rc = sepgsqlClientHasPerms(sid, tclass, required, auname, abort); + + ReleaseSysCache(tuple); + + return rc; +} + +Oid +sepgsql_database_create(const char *datName, Oid srcDatOid, DefElem *newLabel) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + if (!newLabel) + sid = sepgsqlGetDefaultDatabaseSecid(srcDatOid); + else + { + sid.relid = DatabaseRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, + strVal(newLabel->arg)); + } + + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__CREATE, + datName, true); + return sid.secid; +} + +void +sepgsql_database_alter(Oid datOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_database_common(datOid, SEPG_DB_DATABASE__SETATTR, true); +} + +void +sepgsql_database_drop(Oid datOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_database_common(datOid, SEPG_DB_DATABASE__DROP, true); +} + +Oid +sepgsql_database_relabel(Oid datOid, DefElem *newLabel) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + { + if (newLabel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux is disabled now"))); + + return InvalidOid; + } + sid.relid = DatabaseRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, strVal(newLabel->arg)); + + /* db_database:{setattr relabelfrom} to older seclabel */ + sepgsql_database_common(datOid, + SEPG_DB_DATABASE__SETATTR | + SEPG_DB_DATABASE__RELABELFROM, true); + + /* db_database:{relabelto} to newer seclabel */ + sepgsqlClientHasPerms(sid, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__RELABELTO, + get_database_name(datOid), true); + + return sid.secid; +} + +void +sepgsql_database_grant(Oid datOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_database_common(datOid, SEPG_DB_DATABASE__SETATTR, true); +} + +void +sepgsql_database_access(Oid datOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_database_common(datOid, SEPG_DB_DATABASE__ACCESS, true); +} + +void +sepgsql_database_load_module(Oid datOid, const char *filename) +{ + HeapTuple tuple; + security_context_t filecon; + security_context_t datcon; + + if (!sepgsqlIsEnabled()) + return; + /* + * It assumes preloaded libraries are secure, + * because it can be set up using guc variable + * not any SQL statements. + */ + if (GetProcessingMode() == InitProcessing) + return; + + /* Get database context */ + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(datOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for database: %u", datOid); + + datcon = securityRawSecLabelOut(DatabaseRelationId, + HeapTupleGetSecid(tuple)); + ReleaseSysCache(tuple); + + /* Get library context */ + if (getfilecon_raw(filename, &filecon) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not access file \"%s\": %m", filename))); + PG_TRY(); + { + sepgsqlComputePerms(datcon, + filecon, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__LOAD_MODULE, + filename, true); + } + PG_CATCH(); + { + freecon(filecon); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(filecon); +} + +/* ------------------------------------------------------------ * + * + * Pg_namespace related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_schema_common(Oid nspOid, uint32 required, bool abort) +{ + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + const char *auname; + bool rc; + + tuple = SearchSysCache(NAMESPACEOID, + ObjectIdGetDatum(nspOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for namespace: %u", nspOid); + + sid = sepgsqlGetTupleSecid(NamespaceRelationId, tuple, &tclass); + + auname = NameStr(((Form_pg_namespace) GETSTRUCT(tuple))->nspname); + + rc = sepgsqlClientHasPerms(sid, tclass, required, auname, abort); + + ReleaseSysCache(tuple); + + return rc; +} + +Oid +sepgsql_schema_create(const char *nspName, bool isTemp, DefElem *newLabel) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + if (!newLabel) + sid = sepgsqlGetDefaultSchemaSecid(MyDatabaseId); + else + { + sid.relid = NamespaceRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, strVal(newLabel->arg)); + } + + sepgsqlClientHasPerms(sid, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__CREATE, + nspName, true); + return sid.secid; +} + +void +sepgsql_schema_alter(Oid nspOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__SETATTR, true); +} + +void +sepgsql_schema_drop(Oid nspOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__DROP, true); +} + +Oid +sepgsql_schema_relabel(Oid nspOid, DefElem *newLabel) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + { + if (newLabel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux is disabled now"))); + return InvalidOid; + } + sid.relid = NamespaceRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, strVal(newLabel->arg)); + + /* db_schema:{setattr relabelfrom} for older seclabel */ + sepgsql_schema_common(nspOid, + SEPG_DB_SCHEMA__SETATTR | + SEPG_DB_SCHEMA__RELABELFROM, true); + + /* db_schema:{relabelto} for newer seclabel */ + sepgsqlClientHasPerms(sid, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__RELABELTO, + get_namespace_name(nspOid), true); + + return sid.secid; +} + +void +sepgsql_schema_grant(Oid nspOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__SETATTR, true); +} + +bool +sepgsql_schema_search(Oid nspOid, bool abort) +{ + if (!sepgsqlIsEnabled()) + return true; + + return sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__SEARCH, abort); +} + +/* ------------------------------------------------------------ * + * + * Pg_attribute related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_attribute_common(Oid relOid, AttrNumber attnum, + uint32 required, bool abort) +{ + Form_pg_attribute attForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + char auname[NAMEDATALEN * 2 + 3]; + bool rc = true; + + /* Caller prevent case when relkind != RELKIND_RELATION */ + Assert(get_rel_relkind(relOid) == RELKIND_RELATION); + + tuple = SearchSysCache(ATTNUM, + ObjectIdGetDatum(relOid), + Int16GetDatum(attnum), + 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relOid); + attForm = (Form_pg_attribute) GETSTRUCT(tuple); + + /* + * NOTE: when a table to be dropped, corresponding attributes + * are also removed. Some of them can be already logically + * dropped using ALTER TABLE ... DROP statement. + * In this case, SE-PostgreSQL does not check anything. + * If any other situation touches dropped column, it is a bug. + */ + if (attForm->attisdropped) + goto skip; + + sprintf(auname, "%s.%s", get_rel_name(relOid), NameStr(attForm->attname)); + + sid = sepgsqlGetTupleSecid(AttributeRelationId, tuple, &tclass); + + rc = sepgsqlClientHasPerms(sid, tclass, required, auname, abort); + +skip: + ReleaseSysCache(tuple); + + return rc; +} + +Oid +sepgsql_attribute_create(Oid relOid, ColumnDef *cdef) +{ + sepgsql_sid_t sid; + char relkind; + + if (!sepgsqlIsEnabled()) + { + if (cdef->secLabel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux is disabled now"))); + return InvalidOid; + } + + relkind = get_rel_relkind(relOid); + if (relkind == RELKIND_RELATION) + { + char auname[NAMEDATALEN * 2 + 3]; + + if (!cdef->secLabel) + sid = sepgsqlGetDefaultColumnSecid(relOid); + else + { + char *label = strVal(((DefElem *)cdef->secLabel)->arg); + + sid.relid = AttributeRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, label); + } + + sprintf(auname, "%s.%s", get_rel_name(relOid), cdef->colname); + sepgsqlClientHasPerms(sid, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__CREATE, + auname, true); + } + else + { + /* no need to check for toast relation */ + if (relkind != RELKIND_TOASTVALUE) + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); + return InvalidOid; + } + + return sid.secid; +} + +void +sepgsql_attribute_alter(Oid relOid, const char *attname) +{ + AttrNumber attno; + char relkind; + + if (!sepgsqlIsEnabled()) + return; + + /* + * If the target attribute does not exist, an error + * shall be raised later. + */ + attno = get_attnum(relOid, attname); + if (attno == InvalidAttrNumber) + return; + + relkind = get_rel_relkind(relOid); + if (relkind == RELKIND_RELATION) + { + sepgsql_attribute_common(relOid, attno, SEPG_DB_COLUMN__SETATTR, true); + } + else if (relkind != RELKIND_TOASTVALUE) + { + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); + } +} + +void +sepgsql_attribute_drop(Oid relOid, AttrNumber attnum) +{ + if (!sepgsqlIsEnabled()) + return; + + /* + * We only need to check db_column:{drop} when relkind equals + * RELKIND_RELATION, because db_xxx:{drop} permission is already + * checked in other cases. (e.g DROP SEQUENCE, ...) + */ + if (get_rel_relkind(relOid) == RELKIND_RELATION) + sepgsql_attribute_common(relOid, attnum, + SEPG_DB_COLUMN__DROP, true); +} + +void +sepgsql_attribute_grant(Oid relOid, AttrNumber attnum) +{ + char relkind; + + if (!sepgsqlIsEnabled()) + return; + + relkind = get_rel_relkind(relOid); + if (relkind == RELKIND_RELATION) + { + sepgsql_attribute_common(relOid, attnum, SEPG_DB_COLUMN__SETATTR, true); + } + else if (relkind != RELKIND_TOASTVALUE) + { + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); + } +} + +Oid +sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum, DefElem *newLabel) +{ + sepgsql_sid_t sid; + char auname[NAMEDATALEN * 2 + 3]; + + if (!sepgsqlIsEnabled()) + { + if (!newLabel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux is disabled now"))); + return InvalidOid; + } + + Assert(get_rel_relkind(relOid) == RELKIND_RELATION); + + sid.relid = AttributeRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, strVal(newLabel->arg)); + + /* db_column:{setattr relabelfrom} */ + sepgsql_attribute_common(relOid, attnum, + SEPG_DB_COLUMN__SETATTR | + SEPG_DB_COLUMN__RELABELFROM, true); + + /* db_column:{relabelto} */ + sprintf(auname, "%s.%s", + get_rel_name(relOid), + get_attname(relOid, attnum)); + sepgsqlClientHasPerms(sid, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__RELABELTO, + auname, true); + + return sid.secid; +} + +/* ------------------------------------------------------------ * + * + * Pg_class related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_relation_common(Oid relOid, uint32 required, bool abort) +{ + Form_pg_class relForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + bool rc; + + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(relOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relOid); + relForm = (Form_pg_class) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(RelationRelationId, tuple, &tclass); + rc = sepgsqlClientHasPerms(sid, tclass, required, + NameStr(relForm->relname), abort); + + ReleaseSysCache(tuple); + + return rc; +} + +/* + * sepgsql_relation_create + * It returns an array of security identifier for the new table + * and columns to be assigned. The corresponding security labels + * are already checked for db_table/db_sequence/db_column:{create} + * permission. + * In the default labeling rule, a column inherits the security + * label of its table, but we cannot refer it using system caches, + * because the command counter is not incremented under the + * heap_create_with_catalog(). Thus, we need to compute and check + * them prior to the actual creation of table and columns. + */ +Oid * +sepgsql_relation_create(const char *relName, + char relkind, + TupleDesc tupDesc, + Oid nspOid, + DefElem *relLabel, + List *colList, + bool createAs, + bool permission) +{ + Oid *secLabels; + sepgsql_sid_t relsid; + uint16 tclass; + uint32 required; + int index; + + if (!sepgsqlIsEnabled()) + return NULL; + + switch (relkind) + { + case RELKIND_RELATION: + if (!relLabel) + relsid = sepgsqlGetDefaultTableSecid(nspOid); + else + { + relsid.relid = RelationRelationId; + relsid.secid = securityTransSecLabelIn(relsid.relid, + strVal(relLabel->arg)); + } + tclass = SEPG_CLASS_DB_TABLE; + required = SEPG_DB_TABLE__CREATE; + if (createAs) + required |= SEPG_DB_TABLE__INSERT; + break; + + case RELKIND_SEQUENCE: + if (!relLabel) + relsid = sepgsqlGetDefaultSequenceSecid(nspOid); + else + { + relsid.relid = RelationRelationId; + relsid.secid = securityTransSecLabelIn(relsid.relid, + strVal(relLabel->arg)); + } + tclass = SEPG_CLASS_DB_SEQUENCE; + required = SEPG_DB_SEQUENCE__CREATE; + break; + + default: + if (!relLabel) + relsid = sepgsqlGetDefaultTupleSecid(RelationRelationId); + else + { + /* should not be happen */ + relsid.relid = RelationRelationId; + relsid.secid = securityTransSecLabelIn(relsid.relid, + strVal(relLabel->arg)); + } + tclass = SEPG_CLASS_DB_TUPLE; + required = SEPG_DB_TUPLE__INSERT; + break; + } + + /* + * The secLabeld array stores security identifiers to be assigned + * on the new table and columns. + * + * secLabels[0] is security identifier of the table. + * secLabels[attnum - FirstLowInvalidHeapAttributeNumber] + * is security identifier of columns (if necessary). + */ + secLabels = palloc0(sizeof(Oid) * (tupDesc->natts + - FirstLowInvalidHeapAttributeNumber)); + + /* relation's security identifier to be assigned on */ + secLabels[0] = relsid.secid; + + /* + * Note that this hook can be called during initdb processes. + * It is an exception of access controls, so we skip any checks. + * + * And, we don't need any checks for toast relations, because + * it is a quite internal stuff. + */ + if (permission) + { + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + /* db_table:{create}, db_sequence:{create} or db_tuple:{insert} */ + sepgsqlClientHasPerms(relsid, tclass, required, relName, true); + } + + /* no individual security context expect for RELKIND_RELATION */ + if (relkind != RELKIND_RELATION) + return secLabels; + + /* + * db_column:{create} permission + */ + for (index = FirstLowInvalidHeapAttributeNumber + 1; + index < tupDesc->natts; + index++) + { + Form_pg_attribute attr; + sepgsql_sid_t attsid = { InvalidOid, InvalidOid }; + char attname[NAMEDATALEN * 2 + 3]; + ListCell *l; + + /* skip unnecessary attributes */ + if (index == ObjectIdAttributeNumber && !tupDesc->tdhasoid) + continue; + + if (index < 0) + attr = SystemAttributeDefinition(index, tupDesc->tdhasoid); + else + attr = tupDesc->attrs[index]; + + /* Is there any given security context? */ + foreach (l, colList) + { + ColumnDef *cdef = lfirst(l); + + if (cdef->secLabel && + strcmp(cdef->colname, NameStr(attr->attname)) == 0) + { + attsid.relid = AttributeRelationId; + attsid.secid = securityTransSecLabelIn(attsid.relid, + strVal(((DefElem *)cdef->secLabel)->arg)); + break; + } + } + + /* default security context, if not given */ + if (!SidIsValid(attsid)) + attsid = sepgsqlClientCreateSecid(relsid, + SEPG_CLASS_DB_COLUMN, + AttributeRelationId); + if (permission) + { + required = SEPG_DB_COLUMN__CREATE; + + if (createAs) + required |= SEPG_DB_COLUMN__INSERT; + + /* db_column:{create (insert)} */ + sprintf(attname, "%s.%s", relName, NameStr(attr->attname)); + sepgsqlClientHasPerms(attsid, + SEPG_CLASS_DB_COLUMN, + required, attname, true); + } + /* column's security identifier to be assigend on */ + secLabels[index - FirstLowInvalidHeapAttributeNumber] = attsid.secid; + } + + return secLabels; +} + +/* + * sepgsql_relation_copy + * It returns an array of security identifier of table and columns + * to be copied on make_new_heap(). It actually create a new temporary + * relation and insert all the tuples within original one into the + * temporary one, but swap_relation_files() swaps their file nodes. + * Thus, there are no changes from the viewpoint of users. + * SE-PostgreSQL also does not check and change anything. It simply + * copies security identifier of the source relation to the destination + * relation. + */ +Oid * +sepgsql_relation_copy(Relation src) +{ + Oid *secLabels; + HeapTuple tuple; + Oid relOid = RelationGetRelid(src); + int index; + + if (!sepgsqlIsEnabled()) + return NULL; + + /* see the comment at sepgsqlCreateTableColumn*/ + secLabels = palloc0(sizeof(Oid) * (RelationGetDescr(src)->natts + - FirstLowInvalidHeapAttributeNumber)); + + /* copy table's security identifier */ + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(relOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation \"%s\"", + RelationGetRelationName(src)); + + secLabels[0] = HeapTupleGetSecid(tuple); + + ReleaseSysCache(tuple); + + /* copy column's security identifier */ + for (index = FirstLowInvalidHeapAttributeNumber + 1; + index < RelationGetDescr(src)->natts; + index++) + { + Form_pg_attribute attr; + + if (index < 0) + attr = SystemAttributeDefinition(index, true); + else + attr = RelationGetDescr(src)->attrs[index]; + + tuple = SearchSysCache(ATTNUM, + ObjectIdGetDatum(relOid), + Int16GetDatum(attr->attnum), + 0, 0); + if (!HeapTupleIsValid(tuple)) + continue; + + secLabels[index - FirstLowInvalidHeapAttributeNumber] + = HeapTupleGetSecid(tuple); + + ReleaseSysCache(tuple); + } + + return secLabels; +} + +void +sepgsql_relation_alter(Oid relOid, const char *newName, Oid newNsp) +{ + Form_pg_class relForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(relOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relOid); + relForm = (Form_pg_class) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(RelationRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TABLE__SETATTR, + NameStr(relForm->relname), true); + + /* db_schema:{add_name remove_name}, if necessary */ + if (newName || OidIsValid(newNsp)) + { + if (!OidIsValid(newNsp)) + sepgsql_schema_common(relForm->relnamespace, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + else + { + sepgsql_schema_common(relForm->relnamespace, + SEPG_DB_SCHEMA__REMOVE_NAME, true); + sepgsql_schema_common(newNsp, SEPG_DB_SCHEMA__ADD_NAME, true); + } + } + ReleaseSysCache(tuple); +} + +void +sepgsql_relation_drop(Oid relOid) +{ + if (!sepgsqlIsEnabled()) + return; + + if (get_rel_relkind(relOid) == RELKIND_TOASTVALUE) + return; + + sepgsql_relation_common(relOid, SEPG_DB_TABLE__DROP, true); +} + +void +sepgsql_relation_grant(Oid relOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); +} + +Oid +sepgsql_relation_relabel(Oid relOid, DefElem *newLabel) +{ + sepgsql_sid_t sid; + char relkind; + + if (!sepgsqlIsEnabled()) + { + if (newLabel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux is disabled now"))); + return InvalidOid; + } + + relkind = get_rel_relkind(relOid); + if (relkind != RELKIND_RELATION && relkind != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Unable to set security label on \"%s\"", + get_rel_name(relOid)))); + + /* input security context */ + sid.relid = RelationRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, strVal(newLabel->arg)); + + /* db_table/db_sequence:{setattr relabelfrom} */ + sepgsql_relation_common(relOid, + SEPG_DB_TABLE__SETATTR | + SEPG_DB_TABLE__RELABELFROM, true); + + /* db_table/db_sequence:{relabelto} */ + sepgsqlClientHasPerms(sid, + (relkind == RELKIND_RELATION + ? SEPG_CLASS_DB_TABLE + : SEPG_CLASS_DB_SEQUENCE), + SEPG_DB_TABLE__RELABELTO, + get_rel_name(relOid), true); + + return sid.secid; +} + +void +sepgsql_relation_get_transaction_id(Oid relOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_relation_common(relOid, SEPG_DB_TABLE__GETATTR, true); +} + +void +sepgsql_relation_copy_definition(Oid relOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_relation_common(relOid, SEPG_DB_TABLE__GETATTR, true); +} + +void +sepgsql_relation_truncate(Relation rel) +{ + HeapScanDesc scan; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + Assert(RelationGetForm(rel)->relkind == RELKIND_RELATION); + + if (!sepgsqlIsEnabled()) + return; + + /* check db_table:{delete} permission */ + sepgsql_relation_common(RelationGetRelid(rel), + SEPG_DB_TABLE__DELETE, true); + + /* row-level access control is enabled? */ + if (!sepostgresql_row_level) + return; + + /* check db_tuple:{delete} permission */ + scan = heap_beginscan(rel, SnapshotNow, 0, NULL); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + sid = sepgsqlGetTupleSecid(RelationGetRelid(rel), tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + NULL, true); + } + heap_endscan(scan); +} + +void +sepgsql_relation_lock(Oid relOid) +{ + if (!sepgsqlIsEnabled()) + return; + + if (get_rel_relkind(relOid) != RELKIND_RELATION) + return; + + sepgsql_relation_common(relOid, SEPG_DB_TABLE__LOCK, true); +} + +void +sepgsql_view_replace(Oid viewOid) +{ + if (!sepgsqlIsEnabled()) + return; + + Assert(get_rel_relkind(viewOid) == RELKIND_VIEW); + + sepgsql_relation_common(viewOid, SEPG_DB_TABLE__SETATTR, true); +} + +void +sepgsql_index_create(Oid relOid, Oid nspOid) +{ + if (!sepgsqlIsEnabled()) + return; + + /* db_table:{setattr} */ + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); + + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); +} + +void +sepgsql_sequence_get_value(Oid seqOid) +{ + if (!sepgsqlIsEnabled()) + return; + + Assert(get_rel_relkind(seqOid) == RELKIND_SEQUENCE); + + sepgsql_relation_common(seqOid, SEPG_DB_SEQUENCE__GET_VALUE, true); +} + +void +sepgsql_sequence_next_value(Oid seqOid) +{ + if (!sepgsqlIsEnabled()) + return; + + Assert(get_rel_relkind(seqOid) == RELKIND_SEQUENCE); + + sepgsql_relation_common(seqOid, SEPG_DB_SEQUENCE__NEXT_VALUE, true); +} + +void +sepgsql_sequence_set_value(Oid seqOid) +{ + if (!sepgsqlIsEnabled()) + return; + + Assert(get_rel_relkind(seqOid) == RELKIND_SEQUENCE); + + sepgsql_relation_common(seqOid, SEPG_DB_SEQUENCE__SET_VALUE, true); +} + +/* ------------------------------------------------------------ * + * + * Pg_proc related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_proc_common(Oid procOid, uint32 required, bool abort) +{ + sepgsql_sid_t sid; + HeapTuple tuple; + uint16 tclass; + const char *auname; + bool rc; + + tuple = SearchSysCache(PROCOID, + ObjectIdGetDatum(procOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for procedure: %u", procOid); + + auname = NameStr(((Form_pg_proc) GETSTRUCT(tuple))->proname); + sid = sepgsqlGetTupleSecid(ProcedureRelationId, tuple, &tclass); + + rc = sepgsqlClientHasPerms(sid, tclass, required, auname, abort); + + ReleaseSysCache(tuple); + + return rc; +} + +Oid +sepgsql_proc_create(const char *procName, HeapTuple oldTup, + Oid nspOid, Oid langOid, DefElem *newLabel) +{ + sepgsql_sid_t sid; + //HeapTuple tuple; + uint32 required; + //bool trusted; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + if (!HeapTupleIsValid(oldTup)) + { + /* create a new function */ + required = SEPG_DB_PROCEDURE__CREATE; + if (!newLabel) + sid = sepgsqlGetDefaultProcedureSecid(nspOid); + else + { + sid.relid = ProcedureRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, strVal(newLabel->arg)); + } + } + else if (!newLabel) + { + /* replace an existing function, without any label */ + required = SEPG_DB_PROCEDURE__SETATTR; + sid = sepgsqlGetTupleSecid(ProcedureRelationId, oldTup, NULL); + } + else + { + /* replace an existing function, with relabeling */ + sepgsql_proc_common(HeapTupleGetOid(oldTup), + SEPG_DB_PROCEDURE__SETATTR | + SEPG_DB_PROCEDURE__RELABELFROM, true); + + required = SEPG_DB_PROCEDURE__RELABELTO; + sid = sepgsqlGetTupleSecid(ProcedureRelationId, oldTup, NULL); + } + +#if 0 + /* Procedural language is trusted? */ + tuple = SearchSysCache(LANGOID, + ObjectIdGetDatum(langOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for procedural langugage: %u", langOid); + + trusted = ((Form_pg_language) GETSTRUCT(tuple))->lanpltrusted; + if (!trusted) + required |= SEPG_DB_PROCEDURE__UNTRUSTED; + + ReleaseSysCache(tuple); +#endif + + /* check it */ + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_PROCEDURE, + required, procName, true); + + return sid.secid; +} + +void +sepgsql_proc_alter(Oid procOid, const char *newName, Oid newNsp) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_proc_common(procOid, SEPG_DB_PROCEDURE__SETATTR, true); + if (newName || OidIsValid(newNsp)) + { + HeapTuple tuple; + Oid oldNsp; + + tuple = SearchSysCache(PROCOID, + ObjectIdGetDatum(procOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for function %u", procOid); + + oldNsp = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace; + + ReleaseSysCache(tuple); + + if (!OidIsValid(newNsp)) + { + sepgsql_schema_common(oldNsp, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + else + { + sepgsql_schema_common(oldNsp, SEPG_DB_SCHEMA__REMOVE_NAME, true); + sepgsql_schema_common(newNsp, SEPG_DB_SCHEMA__ADD_NAME, true); + } + } +} + +void +sepgsql_proc_drop(Oid procOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_proc_common(procOid, SEPG_DB_PROCEDURE__DROP, true); +} + +void +sepgsql_proc_grant(Oid procOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_proc_common(procOid, SEPG_DB_PROCEDURE__SETATTR, true); +} + +Oid +sepgsql_proc_relabel(Oid procOid, DefElem *newLabel) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + { + if (newLabel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux is disabled now"))); + return InvalidOid; + } + + sid.relid = ProcedureRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, strVal(newLabel->arg)); + + /* db_procedure:{setattr relabelfrom} for older seclabel */ + sepgsql_proc_common(procOid, + SEPG_DB_PROCEDURE__SETATTR | + SEPG_DB_PROCEDURE__RELABELFROM, true); + /* db_procedure:{relabelto} for newer seclabel */ + sepgsqlClientHasPerms(sid, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__RELABELTO, + get_func_name(procOid), true); + return sid.secid; +} + +void +sepgsql_proc_execute(Oid procOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_proc_common(procOid, SEPG_DB_PROCEDURE__EXECUTE, true); +} + +bool +sepgsql_proc_hint_inlined(HeapTuple protup) +{ + security_context_t newcon; + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return true; + + if (!sepgsql_proc_common(HeapTupleGetOid(protup), + SEPG_DB_PROCEDURE__EXECUTE, false)) + return false; + /* + * If the security context of client is unchange + * before or after invocation of the functions, + * it is not a trusted procedure, so it can be + * inlined due to performance purpose. + */ + sid = sepgsqlGetTupleSecid(ProcedureRelationId, protup, NULL); + + newcon = sepgsqlClientCreateLabel(sid, SEPG_CLASS_PROCESS); + + if (strcmp(sepgsqlGetClientLabel(), newcon) == 0) + return true; + + return false; +} + +bool +sepgsql_proc_entrypoint(HeapTuple protup) +{ + security_context_t newcon; + sepgsql_sid_t proSid; + + if (!sepgsqlIsEnabled()) + return false; + + proSid = sepgsqlGetTupleSecid(ProcedureRelationId, + protup, NULL); + + newcon = sepgsqlClientCreateLabel(proSid, SEPG_CLASS_PROCESS); + + /* Do nothing, if it is not a trusted procedure */ + if (strcmp(newcon, sepgsqlGetClientLabel()) == 0) + return false; + + /* check db_procedure:{entrypoint} */ + sepgsqlClientHasPerms(proSid, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__ENTRYPOINT, + NULL, true); + + /* check process:{transition} */ + sepgsqlComputePerms(sepgsqlGetClientLabel(), + newcon, + SEPG_CLASS_PROCESS, + SEPG_PROCESS__TRANSITION, + NULL, true); + + return true; +} + +char * +sepgsql_proc_trusted(HeapTuple protup, MemoryContext mcxt) +{ + MemoryContext oldcxt; + security_context_t newcon; + sepgsql_sid_t proSid; + + if (!sepgsqlIsEnabled()) + return NULL; + + proSid = sepgsqlGetTupleSecid(ProcedureRelationId, protup, NULL); + + oldcxt = MemoryContextSwitchTo(mcxt); + + newcon = sepgsqlClientCreateLabel(proSid, SEPG_CLASS_PROCESS); + + MemoryContextSwitchTo(oldcxt); + + return newcon; +} + +/* ------------------------------------------------------------ * + * + * Pg_cast related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_cast_create(Oid sourceTypOid, Oid targetTypOid, Oid funcOid) +{ + sepgsql_sid_t sid; + char audit_buffer[2*NAMEDATALEN+10]; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(CastRelationId); + + snprintf(audit_buffer, sizeof(audit_buffer), "%s::%s", + format_type_be(sourceTypOid), format_type_be(targetTypOid)); + + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + audit_buffer, true); + + if (OidIsValid(funcOid)) + sepgsql_proc_common(funcOid, SEPG_DB_PROCEDURE__INSTALL, true); + + return sid.secid; +} + +void +sepgsql_cast_drop(Oid castOid) +{ + Form_pg_cast castForm; + Relation rel; + HeapTuple tuple; + ScanKeyData skey; + SysScanDesc scan; + sepgsql_sid_t sid; + uint16 tclass; + char audit_buffer[2*NAMEDATALEN+10]; + + if (!sepgsqlIsEnabled()) + return; + + rel = heap_open(CastRelationId, AccessShareLock); + + ScanKeyInit(&skey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(castOid)); + + scan = systable_beginscan(rel, CastOidIndexId, true, + SnapshotNow, 1, &skey); + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for cast: %u", castOid); + + castForm = (Form_pg_cast) GETSTRUCT(tuple); + + snprintf(audit_buffer, sizeof(audit_buffer), "%s::%s", + format_type_be(castForm->castsource), + format_type_be(castForm->casttarget)); + + sid = sepgsqlGetTupleSecid(CastRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + audit_buffer, true); + + systable_endscan(scan); + + heap_close(rel, AccessShareLock); +} + +/* ------------------------------------------------------------ * + * + * Pg_conversion related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_conversion_create(const char *convName, Oid nspOid, Oid procOid) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(ConversionRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + convName, true); + + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + /* db_procedure:{install} */ + sepgsql_proc_common(procOid, SEPG_DB_PROCEDURE__INSTALL, true); + + return sid.secid; +} + +void +sepgsql_conversion_alter(Oid convOid, const char *newName) +{ + Form_pg_conversion convForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(CONVOID, + ObjectIdGetDatum(convOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for conversion %u", convOid); + convForm = (Form_pg_conversion) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(ConversionRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(convForm->conname), true); + if (newName) + { + Oid nspOid = convForm->connamespace; + + sepgsql_schema_common(nspOid, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + ReleaseSysCache(tuple); +} + +void +sepgsql_conversion_drop(Oid convOid) +{ + Form_pg_conversion convForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(CONVOID, + ObjectIdGetDatum(convOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for conversion %u", convOid); + convForm = (Form_pg_conversion) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(ConversionRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(convForm->conname), true); + + /* db_schema:{remove_name} */ + sepgsql_schema_common(convForm->connamespace, + SEPG_DB_SCHEMA__REMOVE_NAME, true); + + ReleaseSysCache(tuple); +} + +/* ------------------------------------------------------------ * + * + * Pg_foreign_data_wrapper related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_fdw_common(Oid fdwOid, uint32 required, bool abort) +{ + Form_pg_foreign_data_wrapper fdwForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + bool rc; + + tuple = SearchSysCache(FOREIGNDATAWRAPPEROID, + ObjectIdGetDatum(fdwOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for FDW: %u", fdwOid); + fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(ForeignDataWrapperRelationId, tuple, &tclass); + rc = sepgsqlClientHasPerms(sid, tclass, required, + NameStr(fdwForm->fdwname), abort); + ReleaseSysCache(tuple); + + return rc; +} + +Oid +sepgsql_fdw_create(const char *fdwName, Oid fdwValidator) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(ForeignDataWrapperRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + fdwName, true); + + /* db_procedure:{install} */ + if (OidIsValid(fdwValidator)) + sepgsql_proc_common(fdwValidator, SEPG_DB_PROCEDURE__INSTALL, true); + + return sid.secid; +} + +void +sepgsql_fdw_alter(Oid fdwOid, Oid newValidator) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_fdw_common(fdwOid, SEPG_DB_TUPLE__UPDATE, true); + + /* db_procedure:{install} */ + if (OidIsValid(newValidator)) + sepgsql_proc_common(newValidator, SEPG_DB_PROCEDURE__INSTALL, true); +} + +void +sepgsql_fdw_drop(Oid fdwOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_fdw_common(fdwOid, SEPG_DB_TUPLE__DELETE, true); +} + +void +sepgsql_fdw_grant(Oid fdwOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_fdw_common(fdwOid, SEPG_DB_TUPLE__UPDATE, true); +} + +/* ------------------------------------------------------------ * + * + * Pg_foreign_server related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_foreign_server_common(Oid fsrvOid, uint32 required, bool abort) +{ + Form_pg_foreign_server fsrvForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + bool rc; + + tuple = SearchSysCache(FOREIGNSERVEROID, + ObjectIdGetDatum(fsrvOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for foreign server %u", fsrvOid); + fsrvForm = (Form_pg_foreign_server) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(ForeignServerRelationId, tuple, &tclass); + rc = sepgsqlClientHasPerms(sid, tclass, required, + NameStr(fsrvForm->srvname), abort); + ReleaseSysCache(tuple); + + return rc; +} + +Oid +sepgsql_foreign_server_create(const char *fsrvName) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(ForeignServerRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + fsrvName, true); + + return sid.secid; +} + +void +sepgsql_foreign_server_alter(Oid fsrvOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_foreign_server_common(fsrvOid, SEPG_DB_TUPLE__UPDATE, true); +} + +void +sepgsql_foreign_server_drop(Oid fsrvOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_foreign_server_common(fsrvOid, SEPG_DB_TUPLE__DELETE, true); +} + +void +sepgsql_foreign_server_grant(Oid fsrvOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_foreign_server_common(fsrvOid, SEPG_DB_TUPLE__UPDATE, true); +} + +/* ------------------------------------------------------------ * + * + * Pg_language related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_language_common(Oid langOid, uint32 required, bool abort) +{ + Form_pg_language langForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + bool rc; + + tuple = SearchSysCache(LANGOID, + ObjectIdGetDatum(langOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for language %u", langOid); + langForm = (Form_pg_language) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(LanguageRelationId, tuple, &tclass); + rc = sepgsqlClientHasPerms(sid, tclass, required, + NameStr(langForm->lanname), abort); + + ReleaseSysCache(tuple); + + return rc; +} + +Oid +sepgsql_language_create(const char *langName, Oid handlerOid, Oid validatorOid) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(LanguageRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, langName, true); + + /* db_procedure:{install} */ + if (OidIsValid(handlerOid)) + sepgsql_proc_common(handlerOid, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(validatorOid)) + sepgsql_proc_common(validatorOid, SEPG_DB_PROCEDURE__INSTALL, true); + + return sid.secid; +} + +void +sepgsql_language_alter(Oid langOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_language_common(langOid, SEPG_DB_TUPLE__UPDATE, true); +} + +void +sepgsql_language_drop(Oid langOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_language_common(langOid, SEPG_DB_TUPLE__DELETE, true); +} + +void +sepgsql_language_grant(Oid langOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_language_common(langOid, SEPG_DB_TUPLE__UPDATE, true); +} + +/* ------------------------------------------------------------ * + * + * Pg_largeobject related security hooks + * (need to backport v8.5 feature) + * ------------------------------------------------------------ */ +static bool +sepgsql_largeobject_common(Oid loid, uint32 required, Snapshot snapshot) +{ + Relation rel; + ScanKeyData skey; + SysScanDesc scan; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + char auname[64]; + bool rc; + + rel = heap_open(LargeObjectMetadataRelationId, AccessShareLock); + + ScanKeyInit(&skey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(loid)); + + scan = systable_beginscan(rel, LargeObjectMetadataOidIndexId, + true, snapshot, 1, &skey); + + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "largeobject %u lookup failed", loid); + + snprintf(auname, sizeof(auname), "blob:%u", loid); + + sid = sepgsqlGetTupleSecid(RelationGetRelid(rel), tuple, &tclass); + + rc = sepgsqlClientHasPerms(sid, tclass, required, auname, true); + + systable_endscan(scan); + + heap_close(rel, AccessShareLock); + + return rc; +} + +Oid +sepgsql_largeobject_create(Oid loid, Value *secLabel) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + if (!secLabel) + sid = sepgsqlGetDefaultBlobSecid(MyDatabaseId); + else + { + sid.relid = LargeObjectMetadataRelationId; + sid.secid = securityTransSecLabelIn(sid.relid, strVal(secLabel)); + } + + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_BLOB, + SEPG_DB_BLOB__CREATE, + NULL, true); + return sid.secid; +} + +void +sepgsql_largeobject_alter(Oid loid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_largeobject_common(loid, SEPG_DB_BLOB__SETATTR, SnapshotNow); +} + +void +sepgsql_largeobject_drop(Oid loid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_largeobject_common(loid, SEPG_DB_BLOB__DROP, SnapshotNow); +} + +void +sepgsql_largeobject_read(Oid loid, Snapshot snapshot) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_largeobject_common(loid, SEPG_DB_BLOB__READ, snapshot); +} + +void +sepgsql_largeobject_write(Oid loid, Snapshot snapshot) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_largeobject_common(loid, SEPG_DB_BLOB__WRITE, snapshot); +} + +void +sepgsql_largeobject_export(Oid loid, const char *filename) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_largeobject_common(loid, + SEPG_DB_BLOB__READ | + SEPG_DB_BLOB__EXPORT, SnapshotNow); + + sepgsql_file_write(filename); +} + +Oid +sepgsql_largeobject_import(Oid loid, const char *filename) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultBlobSecid(MyDatabaseId); + + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_BLOB, + SEPG_DB_BLOB__CREATE | + SEPG_DB_BLOB__WRITE | + SEPG_DB_BLOB__IMPORT, + NULL, true); + + sepgsql_file_read(filename); + + return sid.secid; +} + +/* ------------------------------------------------------------ * + * + * Pg_opclass related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_opclass_create(const char *opcName, Oid nspOid) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(OperatorClassRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + opcName, true); + + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + return sid.secid; +} + +void +sepgsql_opclass_alter(Oid opcOid, const char *newName) +{ + Form_pg_opclass opcForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(CLAOID, + ObjectIdGetDatum(opcOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for opclass %u", opcOid); + opcForm = (Form_pg_opclass) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(OperatorClassRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(opcForm->opcname), true); + + /* db_schema:{add_name remove_name} */ + if (newName) + { + sepgsql_schema_common(opcForm->opcnamespace, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + ReleaseSysCache(tuple); +} + +void +sepgsql_opclass_drop(Oid opcOid) +{ + Form_pg_opclass opcForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(CLAOID, + ObjectIdGetDatum(opcOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for opclass %u", opcOid); + opcForm = (Form_pg_opclass) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(OperatorClassRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(opcForm->opcname), true); + + /* db_schema:{remove_name} */ + sepgsql_schema_common(opcForm->opcnamespace, + SEPG_DB_SCHEMA__REMOVE_NAME, true); + + ReleaseSysCache(tuple); +} + +/* ------------------------------------------------------------ * + * + * Pg_opfamily related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_opfamily_create(const char *opfName, Oid nspOid) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(OperatorFamilyRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + opfName, true); + + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + return sid.secid; +} + +void +sepgsql_opfamily_alter(Oid opfOid, const char *newName) +{ + Form_pg_opfamily opfForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(OPFAMILYOID, + ObjectIdGetDatum(opfOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for operator family: %u", opfOid); + opfForm = (Form_pg_opfamily) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(OperatorFamilyRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(opfForm->opfname), true); + if (newName) + { + sepgsql_schema_common(opfForm->opfnamespace, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + ReleaseSysCache(tuple); +} + +void +sepgsql_opfamily_drop(Oid opfOid) +{ + Form_pg_opfamily opfForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(OPFAMILYOID, + ObjectIdGetDatum(opfOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for operator family: %u", opfOid); + opfForm = (Form_pg_opfamily) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(OperatorFamilyRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + NameStr(opfForm->opfname), true); + + /* db_schema:{remove_name} */ + sepgsql_schema_common(opfForm->opfnamespace, + SEPG_DB_SCHEMA__REMOVE_NAME, true); + + ReleaseSysCache(tuple); +} + +void +sepgsql_opfamily_add_operator(Oid opfOid, Oid operOid) +{ + if (!sepgsqlIsEnabled()) + return; + + /* currently, do nothing here */ +} + +void +sepgsql_opfamily_add_procedure(Oid opfOid, Oid procOid) +{ + if (!sepgsqlIsEnabled()) + return; + + /* + * Note that db_tuple:{setattr} is already checked at the + * earlier phase, so db_procedure:{install} is only needed. + */ + if (OidIsValid(procOid)) + sepgsql_proc_common(procOid, SEPG_DB_PROCEDURE__INSTALL, true); +} + +/* ------------------------------------------------------------ * + * + * Pg_operator related security hooks + * + * ------------------------------------------------------------ */ +static bool +sepgsql_operator_common(Oid oprOid, uint32 required, bool abort) +{ + Form_pg_operator oprForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + bool rc; + + tuple = SearchSysCache(OPEROID, + ObjectIdGetDatum(oprOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for operator: %u", oprOid); + oprForm = (Form_pg_operator) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(OperatorRelationId, tuple, &tclass); + rc = sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + NameStr(oprForm->oprname), abort); + + ReleaseSysCache(tuple); + + return rc; +} + +Oid +sepgsql_operator_create(const char *oprName, Oid oprOid, Oid nspOid, + Oid codeFn, Oid restFn, Oid joinFn) +{ + HeapTuple tuple; + sepgsql_sid_t sid; + uint32 required; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + if (!OidIsValid(oprOid)) + { + sid = sepgsqlGetDefaultTupleSecid(OperatorRelationId); + required = SEPG_DB_TUPLE__INSERT; + } + else + { + tuple = SearchSysCache(OPEROID, + ObjectIdGetDatum(oprOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for operator %u", oprOid); + + sid = sepgsqlGetTupleSecid(OperatorRelationId, tuple, NULL); + + ReleaseSysCache(tuple); + + required = SEPG_DB_TUPLE__UPDATE; + } + + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + required, oprName, true); + + /* db_schema:{add_name} checks */ + if (!OidIsValid(oprOid)) + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + /* db_procedure:{install} checks */ + if (OidIsValid(codeFn)) + sepgsql_proc_common(codeFn, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(restFn)) + sepgsql_proc_common(restFn, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(joinFn)) + sepgsql_proc_common(joinFn, SEPG_DB_PROCEDURE__INSTALL, true); + + return sid.secid; +} + +void +sepgsql_operator_alter(Oid oprOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_operator_common(oprOid, SEPG_DB_TUPLE__UPDATE, true); +} + +void +sepgsql_operator_drop(Oid oprOid) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_operator_common(oprOid, SEPG_DB_TUPLE__DELETE, true); +} + +/* ------------------------------------------------------------ * + * + * Pg_rewrite related security hooks + * + * ------------------------------------------------------------ */ +void +sepgsql_rule_create(Oid relOid, const char *ruleName) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); +} + +void +sepgsql_rule_drop(Oid relOid, const char *ruleName) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); +} + +/* ------------------------------------------------------------ * + * + * Pg_trigger related security hooks + * + * ------------------------------------------------------------ */ +void +sepgsql_trigger_create(Oid relOid, const char *trigName, Oid procOid) +{ + if (!sepgsqlIsEnabled()) + return; + + /* db_table:{setattr} */ + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); + + /* db_procedure:{install} */ + sepgsql_proc_common(procOid, SEPG_DB_PROCEDURE__INSTALL, true); +} + +void +sepgsql_trigger_alter(Oid relOid, const char *trigName) +{ + if (!sepgsqlIsEnabled()) + return; + + /* db_table:{setattr} */ + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); +} + +void +sepgsql_trigger_drop(Oid relOid, const char *trigName) +{ + if (!sepgsqlIsEnabled()) + return; + + /* db_table:{setattr} */ + sepgsql_relation_common(relOid, SEPG_DB_TABLE__SETATTR, true); +} + +/* ------------------------------------------------------------ * + * + * Pg_type related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_ts_config_create(const char *cfgName, Oid nspOid) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(TSConfigRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + cfgName, true); + + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + return sid.secid; +} + +void +sepgsql_ts_config_alter(Oid cfgOid, const char *newName) +{ + Form_pg_ts_config cfgForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TSCONFIGOID, + ObjectIdGetDatum(cfgOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search dictionary %u", cfgOid); + cfgForm = (Form_pg_ts_config) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TSConfigRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(cfgForm->cfgname), true); + if (newName) + { + sepgsql_schema_common(cfgForm->cfgnamespace, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + ReleaseSysCache(tuple); +} + +void +sepgsql_ts_config_drop(Oid cfgOid) +{ + Form_pg_ts_config cfgForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TSCONFIGOID, + ObjectIdGetDatum(cfgOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search dictionary %u", cfgOid); + cfgForm = (Form_pg_ts_config) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TSConfigRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + NameStr(cfgForm->cfgname), true); + + /* db_schema:{remove_name} */ + sepgsql_schema_common(cfgForm->cfgnamespace, + SEPG_DB_SCHEMA__REMOVE_NAME, true); + + ReleaseSysCache(tuple); +} + +/* ------------------------------------------------------------ * + * + * Pg_type related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_ts_dict_create(const char *dictName, Oid nspOid) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(TSDictionaryRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + dictName, true); + + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + return sid.secid; +} + +void +sepgsql_ts_dict_alter(Oid dictOid, const char *newName) +{ + Form_pg_ts_dict dictForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TSDICTOID, + ObjectIdGetDatum(dictOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search dictionary %u", dictOid); + dictForm = (Form_pg_ts_dict) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TSDictionaryRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(dictForm->dictname), true); + + /* db_schema:{add_name remove_name} */ + if (newName) + { + sepgsql_schema_common(dictForm->dictnamespace, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + ReleaseSysCache(tuple); +} + +void +sepgsql_ts_dict_drop(Oid dictOid) +{ + Form_pg_ts_dict dictForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TSDICTOID, + ObjectIdGetDatum(dictOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search dictionary %u", dictOid); + dictForm = (Form_pg_ts_dict) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TSDictionaryRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + NameStr(dictForm->dictname), true); + + /* db_schema:{remove_name} */ + sepgsql_schema_common(dictForm->dictnamespace, + SEPG_DB_SCHEMA__REMOVE_NAME, true); + + ReleaseSysCache(tuple); +} + +/* ------------------------------------------------------------ * + * + * Pg_type related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_ts_parser_create(const char *prsName, Oid nspOid, + Oid startFn, Oid tokenFn, Oid sendFn, + Oid headlineFn, Oid lextypeFn) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(TSParserRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + prsName, true); + + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + /* db_procedure:{install} */ + if (OidIsValid(startFn)) + sepgsql_proc_common(startFn, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(tokenFn)) + sepgsql_proc_common(tokenFn, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(sendFn)) + sepgsql_proc_common(sendFn, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(headlineFn)) + sepgsql_proc_common(headlineFn, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(lextypeFn)) + sepgsql_proc_common(lextypeFn, SEPG_DB_PROCEDURE__INSTALL, true); + + return sid.secid; +} + +void +sepgsql_ts_parser_alter(Oid prsOid, const char *newName) +{ + Form_pg_ts_parser prsForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TSPARSEROID, + ObjectIdGetDatum(prsOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search parser %u", prsOid); + + prsForm = (Form_pg_ts_parser) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TSParserRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(prsForm->prsname), true); + if (newName) + { + sepgsql_schema_common(prsForm->prsnamespace, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + ReleaseSysCache(tuple); +} + +void +sepgsql_ts_parser_drop(Oid prsOid) +{ + Form_pg_ts_parser prsForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TSPARSEROID, + ObjectIdGetDatum(prsOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search parser %u", prsOid); + + prsForm = (Form_pg_ts_parser) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TSParserRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + NameStr(prsForm->prsname), true); + + /* db_schema:{remove_name} */ + sepgsql_schema_common(prsForm->prsnamespace, + SEPG_DB_SCHEMA__REMOVE_NAME, true); + + ReleaseSysCache(tuple); +} + +/* ------------------------------------------------------------ * + * + * Pg_type related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_ts_template_create(const char *tmplName, Oid nspOid, + Oid initFn, Oid lexizeFn) +{ + sepgsql_sid_t sid; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + sid = sepgsqlGetDefaultTupleSecid(TSTemplateRelationId); + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + SEPG_DB_TUPLE__INSERT, + tmplName, true); + + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + /* db_procedure:{install} */ + if (OidIsValid(initFn)) + sepgsql_proc_common(initFn, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(lexizeFn)) + sepgsql_proc_common(lexizeFn, SEPG_DB_PROCEDURE__INSTALL, true); + + return sid.secid; +} + +void +sepgsql_ts_template_alter(Oid tmplOid, const char *newName) +{ + Form_pg_ts_template tmplForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TSTEMPLATEOID, + ObjectIdGetDatum(tmplOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search template %u", tmplOid); + tmplForm = (Form_pg_ts_template) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TSTemplateRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(tmplForm->tmplname), true); + if (newName) + { + sepgsql_schema_common(tmplForm->tmplnamespace, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + ReleaseSysCache(tuple); +} + +void +sepgsql_ts_template_drop(Oid tmplOid) +{ + Form_pg_ts_template tmplForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TSTEMPLATEOID, + ObjectIdGetDatum(tmplOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search template %u", tmplOid); + tmplForm = (Form_pg_ts_template) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TSTemplateRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + NameStr(tmplForm->tmplname), true); + + /* db_schema:{remove_name} */ + sepgsql_schema_common(tmplForm->tmplnamespace, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + + ReleaseSysCache(tuple); +} + +/* ------------------------------------------------------------ * + * + * Pg_type related security hooks + * + * ------------------------------------------------------------ */ +Oid +sepgsql_type_create(const char *typName, HeapTuple oldTup, Oid nspOid, + Oid inputProc, Oid outputProc, Oid recvProc, Oid sendProc, + Oid modinProc, Oid modoutProc, Oid analyzeProc) +{ + sepgsql_sid_t sid; + uint32 required; + + if (!sepgsqlIsEnabled()) + return InvalidOid; + + if (!HeapTupleIsValid(oldTup)) + { + sid = sepgsqlGetDefaultTupleSecid(TypeRelationId); + required = SEPG_DB_TUPLE__INSERT; + } + else + { + sid = sepgsqlGetTupleSecid(TypeRelationId, oldTup, NULL); + required = SEPG_DB_TUPLE__UPDATE; + } + sepgsqlClientHasPerms(sid, SEPG_CLASS_DB_TUPLE, + required, typName, true); + /* db_schema:{add_name} */ + sepgsql_schema_common(nspOid, SEPG_DB_SCHEMA__ADD_NAME, true); + + /* db_procedure:{install} */ + if (OidIsValid(inputProc)) + sepgsql_proc_common(inputProc, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(outputProc)) + sepgsql_proc_common(outputProc, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(recvProc)) + sepgsql_proc_common(recvProc, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(sendProc)) + sepgsql_proc_common(sendProc, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(modinProc)) + sepgsql_proc_common(modinProc, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(modoutProc)) + sepgsql_proc_common(modoutProc, SEPG_DB_PROCEDURE__INSTALL, true); + if (OidIsValid(analyzeProc)) + sepgsql_proc_common(analyzeProc, SEPG_DB_PROCEDURE__INSTALL, true); + + return sid.secid; +} + +void +sepgsql_type_alter(Oid typOid, const char *newName, Oid newNsp) +{ + Form_pg_type typForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TYPEOID, + ObjectIdGetDatum(typOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for type: %u", typOid); + typForm = (Form_pg_type) GETSTRUCT(tuple); + + sid = sepgsqlGetTupleSecid(TypeRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__UPDATE, + NameStr(typForm->typname), true); + + if (newName || OidIsValid(newNsp)) + { + Oid oldNsp = typForm->typnamespace; + + if (!OidIsValid(newNsp)) + { + sepgsql_schema_common(oldNsp, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, true); + } + else + { + sepgsql_schema_common(oldNsp, SEPG_DB_SCHEMA__REMOVE_NAME, true); + sepgsql_schema_common(newNsp, SEPG_DB_SCHEMA__ADD_NAME, true); + } + } + ReleaseSysCache(tuple); +} + +void +sepgsql_type_drop(Oid typOid) +{ + Form_pg_type typForm; + HeapTuple tuple; + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + tuple = SearchSysCache(TYPEOID, + ObjectIdGetDatum(typOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for type: %u", typOid); + typForm = (Form_pg_type) GETSTRUCT(tuple); + + if (typForm->typtype == TYPTYPE_COMPOSITE || + (typForm->typtype == TYPTYPE_BASE && OidIsValid(typForm->typarray))) + { + /* + * No need to check for composite type and implicitly + * declared array type here. + */ + ReleaseSysCache(tuple); + return; + } + + sid = sepgsqlGetTupleSecid(TypeRelationId, tuple, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__DELETE, + NameStr(typForm->typname), true); + + /* db_schema:{remove_name} */ + sepgsql_schema_common(typForm->typnamespace, + SEPG_DB_SCHEMA__REMOVE_NAME, true); + + ReleaseSysCache(tuple); +} + +/* ------------------------------------------------------------ * + * + * Misc system object related security hooks + * + * ------------------------------------------------------------ */ + +void +sepgsql_sysobj_drop(const ObjectAddress *object) +{ + switch (object->classId) + { + case RelationRelationId: + if (object->objectSubId == 0) + sepgsql_relation_drop(object->objectId); + else + sepgsql_attribute_drop(object->objectId, + object->objectSubId); + break; + + case ProcedureRelationId: + sepgsql_proc_drop(object->objectId); + break; + + case TypeRelationId: + sepgsql_type_drop(object->objectId); + break; + + case CastRelationId: + sepgsql_cast_drop(object->objectId); + break; + + case ConversionRelationId: + sepgsql_conversion_drop(object->objectId); + break; + + case LanguageRelationId: + sepgsql_language_drop(object->objectId); + break; + + case OperatorRelationId: + sepgsql_operator_drop(object->objectId); + break; + + case OperatorClassRelationId: + sepgsql_opclass_drop(object->objectId); + break; + + case OperatorFamilyRelationId: + sepgsql_opfamily_drop(object->objectId); + break; + + case NamespaceRelationId: + sepgsql_schema_drop(object->objectId); + break; + + case TSParserRelationId: + sepgsql_ts_parser_drop(object->objectId); + break; + + case TSDictionaryRelationId: + sepgsql_ts_dict_drop(object->objectId); + break; + + case TSTemplateRelationId: + sepgsql_ts_template_drop(object->objectId); + break; + + case TSConfigRelationId: + sepgsql_ts_config_drop(object->objectId); + break; + + case AuthIdRelationId: + break; + + case DatabaseRelationId: + sepgsql_database_drop(object->objectId); + break; + + case TableSpaceRelationId: + break; + + case ForeignDataWrapperRelationId: + sepgsql_fdw_drop(object->objectId); + break; + + case ForeignServerRelationId: + sepgsql_foreign_server_drop(object->objectId); + break; + + case UserMappingRelationId: + break; + + default: + /* do nothing */ + break; + } +} + +/* ------------------------------------------------------------ * + * + * Filesystem object related security hooks + * + * ------------------------------------------------------------ */ +static char * +sepgsql_getfilecon(const char *path) +{ + security_context_t context; + char *result; + + if (getfilecon_raw(path, &context) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not get context of \"%s\": %m", path))); + + PG_TRY(); + { + result = pstrdup(context); + } + PG_CATCH(); + { + freecon(context); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(context); + + return result; +} + +static void +sepgsql_file_common(const char *filename, uint32 required, bool may_create) +{ + struct stat stbuf; + + if (stat(filename, &stbuf) == 0) + { + uint16 tclass; + + /* + * Get file object class + */ + if (S_ISDIR(stbuf.st_mode)) + tclass = SEPG_CLASS_DIR; + else if (S_ISCHR(stbuf.st_mode)) + tclass = SEPG_CLASS_CHR_FILE; + else if (S_ISBLK(stbuf.st_mode)) + tclass = SEPG_CLASS_BLK_FILE; + else if (S_ISFIFO(stbuf.st_mode)) + tclass = SEPG_CLASS_FIFO_FILE; + else if (S_ISLNK(stbuf.st_mode)) + tclass = SEPG_CLASS_LNK_FILE; + else if (S_ISSOCK(stbuf.st_mode)) + tclass = SEPG_CLASS_SOCK_FILE; + else + tclass = SEPG_CLASS_FILE; + + /* + * Check permission (no cached operation) + */ + sepgsqlComputePerms(sepgsqlGetClientLabel(), + sepgsql_getfilecon(filename), + tclass, required, + filename, true); + } + else if (may_create) + { + /* + * If the required file is not found, we check permission to + * create a new file and required permission on the new file. + */ + security_context_t dcontext; + security_context_t ncontext; + char *copy = pstrdup(filename); + + /* + * Compute a security context for the new file + */ + dcontext = sepgsql_getfilecon(dirname(copy)); + + ncontext = sepgsqlComputeCreate(sepgsqlGetServerLabel(), + dcontext, + SEPG_CLASS_FILE); + /* + * Check permission (no cached operation) + */ + required |= SEPG_FILE__CREATE; + + sepgsqlComputePerms(sepgsqlGetClientLabel(), + sepgsql_getfilecon(filename), + SEPG_CLASS_FILE, + required, filename, true); + } + else + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + } +} + +void +sepgsql_file_stat(const char *filename) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_file_common(filename, SEPG_FILE__GETATTR, false); +} + +void +sepgsql_file_read(const char *filename) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_file_common(filename, SEPG_FILE__READ, false); +} + +void +sepgsql_file_write(const char *filename) +{ + if (!sepgsqlIsEnabled()) + return; + + sepgsql_file_common(filename, SEPG_FILE__WRITE, true); +} + +/* + * TODO: add check for pg_ls_dir() + */ diff --git a/src/backend/security/sepgsql/checker.c b/src/backend/security/sepgsql/checker.c new file mode 100644 index 0000000..9e573c3 --- /dev/null +++ b/src/backend/security/sepgsql/checker.c @@ -0,0 +1,432 @@ +/* + * src/backend/security/sepgsql/checker.c + * walks on given Query tree and applies checks + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "access/sysattr.h" +#include "catalog/catalog.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_security.h" +#include "miscadmin.h" +#include "security/sepgsql.h" +#include "storage/bufmgr.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/tqual.h" + +/* + * fixupWholeRowReference + */ +static Bitmapset * +fixupWholeRowReference(Oid relid, int nattrs, Bitmapset *columns) +{ + Bitmapset *result; + AttrNumber attno; + + attno = InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber; + + if (!bms_is_member(attno, columns)) + return columns; /* no need to fixup */ + + result = bms_copy(columns); + result = bms_del_member(result, attno); + + for (attno=1; attno <= nattrs; attno++) + { + Form_pg_attribute attform; + HeapTuple atttup; + + atttup = SearchSysCache(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attno), + 0, 0); + if (!HeapTupleIsValid(atttup)) + continue; + + attform = (Form_pg_attribute) GETSTRUCT(atttup); + if (!attform->attisdropped) + { + int cindex = attno - FirstLowInvalidHeapAttributeNumber; + result = bms_add_member(result, cindex); + } + ReleaseSysCache(atttup); + } + + return result; +} + +/* + * checkTabelColumnPerms + * This functions applies table/column level permissions for + * all the appeared ones in user's query, and raises an error + * if violated. + * It also applies a few hardwired policy which prevent to + * modified some of system catalogs. + */ +static void +checkTabelColumnPerms(Oid relid, Bitmapset *selected, Bitmapset *modified, + access_vector_t required) +{ + Bitmapset *columns; + Bitmapset *selected_ex; + Bitmapset *modified_ex; + Form_pg_class relForm; + HeapTuple reltup; + sepgsql_sid_t relsid; + sepgsql_sid_t attsid; + AttrNumber attno; + uint16 tclass; + + /* + * Hardwired Policy: + * SE-PostgreSQL enforces that clients cannot modify system + * catalogs and access toast values using DML statements, + * except initial setting up phase. + */ + if (sepgsqlGetEnforce()) + { + if (IsSystemNamespace(get_rel_namespace(relid)) && + (required & (SEPG_DB_TABLE__UPDATE | + SEPG_DB_TABLE__INSERT | + SEPG_DB_TABLE__DELETE)) != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SE-PostgreSQL prevents to modidy \"%s\"", + get_rel_name(relid)))); + if (get_rel_relkind(relid) == RELKIND_TOASTVALUE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SE-PostgreSQL prevents to access \"%s\"", + get_rel_name(relid)))); + } + + /* + * Check db_table:{...} or db_sequence permissions + */ + reltup = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "SELinux: cache lookup failed for relation %u", relid); + + relForm = (Form_pg_class) GETSTRUCT(reltup); + + relsid = sepgsqlGetTupleSecid(RelationRelationId, reltup, &tclass); + + if (tclass != SEPG_CLASS_DB_TABLE) + { + /* check db_sequence:{xxx} permission */ + if (tclass == SEPG_CLASS_DB_SEQUENCE) + { + if (required & SEPG_DB_TABLE__SELECT) + { + sepgsqlClientHasPerms(relsid, tclass, + SEPG_DB_SEQUENCE__GET_VALUE, + NameStr(relForm->relname), true); + } + } + ReleaseSysCache(reltup); + return; + } + sepgsqlClientHasPerms(relsid, tclass, required, + NameStr(relForm->relname), true); + + /* + * Check db_column:{...} permissions + */ + selected_ex = fixupWholeRowReference(relid, relForm->relnatts, selected); + modified_ex = fixupWholeRowReference(relid, relForm->relnatts, modified); + columns = bms_union(selected_ex, modified_ex); + + while ((attno = bms_first_member(columns)) >= 0) + { + Form_pg_attribute attForm; + HeapTuple atttup; + uint32 attperms = 0; + char auname[2 * NAMEDATALEN + 3]; + + if (bms_is_member(attno, selected_ex)) + attperms |= SEPG_DB_COLUMN__SELECT; + if (bms_is_member(attno, modified_ex)) + { + if (required & SEPG_DB_TABLE__UPDATE) + attperms |= SEPG_DB_COLUMN__UPDATE; + if (required & SEPG_DB_TABLE__INSERT) + attperms |= SEPG_DB_COLUMN__INSERT; + } + if (attperms == 0) + continue; + + /* remove the attribute number offset */ + attno += FirstLowInvalidHeapAttributeNumber; + atttup = SearchSysCache(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attno), + 0, 0); + if (!HeapTupleIsValid(atttup)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attno, relid); + + attForm = (Form_pg_attribute) GETSTRUCT(atttup); + if (attForm->attisdropped) + elog(ERROR, "attribute %d of relation %u does not exist", + attno, relid); + + snprintf(auname, sizeof(auname), "%s.%s", + NameStr(relForm->relname), + NameStr(attForm->attname)); + attsid = sepgsqlGetTupleSecid(AttributeRelationId, + atttup, &tclass); + sepgsqlClientHasPerms(attsid, tclass, attperms, auname, true); + + ReleaseSysCache(atttup); + } + + ReleaseSysCache(reltup); + + if (selected_ex != selected) + bms_free(selected_ex); + + if (modified_ex != modified) + bms_free(modified_ex); + + bms_free(columns); +} + +/* + * sepgsqlCheckQueryPerms + * It checks permission for all the required tables/columns on + * generic user queries. + */ +void +sepgsqlCheckRTEPerms(RangeTblEntry *rte) +{ + access_vector_t required = 0; + + if (!sepgsqlIsEnabled()) + return; + + if (rte->rtekind != RTE_RELATION) + return; + + 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) + { + /* + * ACL_SELECT_FOR_UPDATE is defined as an aliase of ACL_UPDATE, + * so we cannot determine whether the given relation is accessed + * with UPDATE statement or SELECT FOR SHARE/UPDATE immediately. + * UPDATE statements set a bit on rte->modifiedCols at least, + * so we use it as a watermark. + */ + 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; + + if (required == 0) + return; + + checkTabelColumnPerms(rte->relid, + rte->selectedCols, + rte->modifiedCols, + required); +} + +/* + * sepgsqlCheckCopyTable + * It checks permissions on COPY TO/FROM. + */ +void +sepgsqlCheckCopyTable(Relation rel, List *attnumlist, bool is_from) +{ + Bitmapset *selected = NULL; + Bitmapset *modified = NULL; + ListCell *l; + + if (!sepgsqlIsEnabled()) + return; + + /* all checkes are done in sepgsqlCheckRTEPerms */ + if (!rel) + return; + + foreach (l, attnumlist) + { + AttrNumber attno = lfirst_int(l); + + attno -= FirstLowInvalidHeapAttributeNumber; + if (is_from) + modified = bms_add_member(modified, attno); + else + selected = bms_add_member(selected, attno); + } + + checkTabelColumnPerms(RelationGetRelid(rel), + selected, modified, + is_from ? SEPG_DB_TABLE__INSERT + : SEPG_DB_TABLE__SELECT); +} + +/* + * sepgsqlExecScan + * makes a decision on the given tuple. + */ +bool +sepgsqlExecScan(Relation rel, HeapTuple tuple, uint32 required, bool abort) +{ + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled() || + !required || + RelationGetForm(rel)->relkind != RELKIND_RELATION || + RelationGetRelid(rel) == SecurityRelationId) + return true; + + sid = sepgsqlGetTupleSecid(RelationGetRelid(rel), tuple, &tclass); + /* + * Insert/Delete to an external attribute is equivalent to + * the set-attribute on the master + */ + if (sid.relid != RelationGetRelid(rel) && + (required & (SEPG_DB_TUPLE__INSERT | SEPG_DB_TUPLE__DELETE))) + { + required &= ~(SEPG_DB_TUPLE__INSERT | SEPG_DB_TUPLE__DELETE); + required |= SEPG_DB_TUPLE__UPDATE; + } + + return sepgsqlClientHasPerms(sid, tclass, required, NULL, abort); +} + +uint32 +sepgsqlSetupTuplePerms(RangeTblEntry *rte) +{ + AclMode perms = 0; + + if (!sepgsqlIsEnabled()) + return 0; + + if (rte->rtekind != RTE_RELATION) + return 0; + + if (rte->requiredPerms & ACL_SELECT) + perms |= SEPG_DB_TUPLE__SELECT; + if (rte->requiredPerms & ACL_UPDATE && !bms_is_empty(rte->modifiedCols)) + perms |= SEPG_DB_TUPLE__UPDATE; + if (rte->requiredPerms & ACL_DELETE) + perms |= SEPG_DB_TUPLE__DELETE; + + /* + * Special case in pg_largeobject + */ + if (rte->relid == LargeObjectRelationId && + bms_is_member(Anum_pg_largeobject_data + - FirstLowInvalidHeapAttributeNumber, + rte->selectedCols)) + perms |= SEPG_DB_BLOB__READ; + + return perms; +} + +/* + * sepgsqlHeapTupleInsert + * It assigns a default security label, if no explicit security labels + * were given. In addition, it also checks db_tuple:{insert} for the + * tuple newly inserted, when it invoked from user's query. + */ +void +sepgsqlHeapTupleInsert(Relation rel, HeapTuple newtup, bool internal) +{ + sepgsql_sid_t sid; + uint16 tclass; + + if (!sepgsqlIsEnabled()) + return; + + /* + * Assign a default security label, if necessary + */ + if (HeapTupleHasSecid(newtup) && + !OidIsValid(HeapTupleGetSecid(newtup))) + sepgsqlSetDefaultSecid(rel, newtup); + + /* + * It does not check permission for the new tuples + * inserted by system internal stuff using + * simple_heap_insert(); + */ + if (internal) + return; + + sid = sepgsqlGetTupleSecid(RelationGetRelid(rel), + newtup, &tclass); + sepgsqlClientHasPerms(sid, tclass, SEPG_DB_TUPLE__INSERT, NULL, true); +} + +/* + * sepgsqlHeapTupleUpdate + * It checks db_tuple:{relabelfrom relabelto} permission on + * the user queries. (Please note that it does not check + * system internal stuff via simple_heap_update) + */ +void +sepgsqlHeapTupleUpdate(Relation rel, ItemPointer otid, HeapTuple newtup) +{ + Oid secid; + HeapTupleData oldtup; + Buffer oldbuf; + + if (!sepgsqlIsEnabled()) + return; + + /* + * heap_update() preserves the original security label + * of the given tuple, if no explicit security label + * is assigned on the newer version. + * In this case, db_tuple:{update} is already checked + * at the sepgsqlExecScan() hook, so we don't need to + * check anything more. + */ + secid = HeapTupleGetSecid(newtup); + if (!OidIsValid(secid)) + return; + + /* + * User gave an explicit security label + */ + ItemPointerCopy(otid, &oldtup.t_self); + if (!heap_fetch(rel, SnapshotAny, &oldtup, &oldbuf, false, NULL)) + elog(ERROR, "failed to fetch old version of the tuple"); + + if (secid != HeapTupleGetSecid(&oldtup)) + { + sepgsql_sid_t sid; + uint16 tclass; + + /* db_tuple:{relabelfrom} for older security context */ + sid = sepgsqlGetTupleSecid(RelationGetRelid(rel), + &oldtup, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__RELABELFROM, + NULL, true); + + /* db_tuple:{relabelto} for newer security label */ + sid = sepgsqlGetTupleSecid(RelationGetRelid(rel), + newtup, &tclass); + sepgsqlClientHasPerms(sid, tclass, + SEPG_DB_TUPLE__RELABELTO, + NULL, true); + } + ReleaseBuffer(oldbuf); +} diff --git a/src/backend/security/sepgsql/dummy.c b/src/backend/security/sepgsql/dummy.c new file mode 100644 index 0000000..6df24d3 --- /dev/null +++ b/src/backend/security/sepgsql/dummy.c @@ -0,0 +1,79 @@ +/* + * src/backend/utils/sepgsql/dummy.c + * A set of stubs when SE-PostgreSQL is not activated + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "security/sepgsql.h" + +static Datum +unavailable_function(const char *fn_name) +{ + ereport(ERROR, + (errcode(ERRCODE_SELINUX_ERROR), + errmsg("function \"%s\" is not available", fn_name))); + PG_RETURN_VOID(); +} + +Datum +sepgsql_getcon(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_server_getcon(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_get_user(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_get_role(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_get_type(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_get_range(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_set_user(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_set_role(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_set_type(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} + +Datum +sepgsql_set_range(PG_FUNCTION_ARGS) +{ + return unavailable_function(__FUNCTION__); +} diff --git a/src/backend/security/sepgsql/label.c b/src/backend/security/sepgsql/label.c new file mode 100644 index 0000000..e91f8c9 --- /dev/null +++ b/src/backend/security/sepgsql/label.c @@ -0,0 +1,1213 @@ +/* + * src/backend/security/sepgsql/label.c + * SE-PostgreSQL security label management + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "access/sysattr.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/pg_constraint.h" +#include "catalog/heap.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_attrdef.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_auth_members.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_class.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_database.h" +#include "catalog/pg_description.h" +#include "catalog/pg_enum.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_largeobject_metadata.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_security.h" +#include "catalog/pg_shdescription.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_config_map.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pg_type.h" +#include "catalog/pg_user_mapping.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "security/sepgsql.h" +#include "storage/fd.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/tqual.h" + +/* GUC: to turn on/off row level controls in SE-PostgreSQL */ +bool sepostgresql_row_level; + +/* GUC parameter to turn on/off mcstrans */ +bool sepostgresql_mcstrans; + +/* + * sepgsqlTupleDescHasSecid + * + * returns a hint whether we should allocate a field to store + * security label on the given relation, or not. + */ +bool +sepgsqlTupleDescHasSecid(Oid relid, char relkind) +{ + /* + * sepgsqlIsEnabled() is not available because it always returns + * false in bootstraping mode + */ + if (sepostgresql_mode == SEPGSQL_MODE_DISABLED || + is_selinux_enabled() < 1) + return false; + + if (!OidIsValid(relid)) + return sepostgresql_row_level; /* Target of SELECT INTO */ + + /* These system catalogs always have its secid */ + if (relid == DatabaseRelationId || + relid == NamespaceRelationId || + relid == RelationRelationId || + relid == AttributeRelationId || + relid == ProcedureRelationId) + return true; + + /* These system catalogs are an external attributes */ + if (relid == AggregateRelationId || + relid == AccessMethodOperatorRelationId || + relid == AccessMethodProcedureRelationId || + relid == AttrDefaultRelationId || + relid == AuthMemRelationId || + relid == ConstraintRelationId || + relid == DescriptionRelationId || + relid == EnumRelationId || + relid == IndexRelationId || + relid == InheritsRelationId || + relid == LargeObjectRelationId || + relid == RewriteRelationId || + relid == SecurityRelationId || + relid == SharedDescriptionRelationId || + relid == StatisticRelationId || + relid == TriggerRelationId) + return false; + + return sepostgresql_row_level; +} + +/* + * defaultSecidWithXXXX + */ +static sepgsql_sid_t +defaultSecidWithDatabase(Oid relOid, Oid datOid, uint16 tclass) +{ + HeapTuple tuple; + sepgsql_sid_t datSid; + + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(datOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for database: %u", datOid); + + datSid.relid = DatabaseRelationId; + datSid.secid = HeapTupleGetSecid(tuple); + + ReleaseSysCache(tuple); + + return sepgsqlClientCreateSecid(datSid, tclass, relOid); +} + +static sepgsql_sid_t +defaultSecidWithSchema(Oid relOid, Oid nspOid, uint16 tclass) +{ + HeapTuple tuple; + sepgsql_sid_t nspSid; + + tuple = SearchSysCache(NAMESPACEOID, + ObjectIdGetDatum(nspOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for schema: %u", nspOid); + + nspSid.relid = NamespaceRelationId; + nspSid.secid = HeapTupleGetSecid(tuple); + + ReleaseSysCache(tuple); + + return sepgsqlClientCreateSecid(nspSid, tclass, relOid); +} + +static sepgsql_sid_t +defaultSecidWithTable(Oid relOid, Oid tblOid, uint16 tclass) +{ + HeapTuple tuple; + sepgsql_sid_t tblSid; + + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(tblOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation: %u", tblOid); + + tblSid.relid = RelationRelationId; + tblSid.secid = HeapTupleGetSecid(tuple); + + ReleaseSysCache(tuple); + + return sepgsqlClientCreateSecid(tblSid, tclass, relOid); +} + +/* + * sepgsqlGetDefaultDatabaseSecid + * It returns the default security label of a database object. + */ +sepgsql_sid_t +sepgsqlGetDefaultDatabaseSecid(Oid source_database_oid) +{ + return defaultSecidWithDatabase(DatabaseRelationId, + source_database_oid, + SEPG_CLASS_DB_DATABASE); +} + +sepgsql_sid_t +sepgsqlGetDefaultSchemaSecid(Oid database_oid) +{ + return defaultSecidWithDatabase(NamespaceRelationId, + database_oid, + SEPG_CLASS_DB_SCHEMA); +} + +sepgsql_sid_t +sepgsqlGetDefaultTableSecid(Oid namespace_oid) +{ + return defaultSecidWithSchema(RelationRelationId, + namespace_oid, + SEPG_CLASS_DB_TABLE); +} + +sepgsql_sid_t +sepgsqlGetDefaultSequenceSecid(Oid namespace_oid) +{ + return defaultSecidWithSchema(RelationRelationId, + namespace_oid, + SEPG_CLASS_DB_SEQUENCE); +} + +sepgsql_sid_t +sepgsqlGetDefaultProcedureSecid(Oid namespace_oid) +{ + return defaultSecidWithSchema(ProcedureRelationId, + namespace_oid, + SEPG_CLASS_DB_PROCEDURE); +} + +sepgsql_sid_t +sepgsqlGetDefaultColumnSecid(Oid table_oid) +{ + return defaultSecidWithTable(AttributeRelationId, + table_oid, + SEPG_CLASS_DB_COLUMN); +} + +sepgsql_sid_t +sepgsqlGetDefaultTupleSecid(Oid table_oid) +{ + return defaultSecidWithTable(table_oid, + table_oid, + SEPG_CLASS_DB_TUPLE); +} + +sepgsql_sid_t +sepgsqlGetDefaultBlobSecid(Oid database_oid) +{ + return defaultSecidWithDatabase(LargeObjectMetadataRelationId, + MyDatabaseId, + SEPG_CLASS_DB_BLOB); +} + +void +sepgsqlSetDefaultSecid(Relation rel, HeapTuple tuple) +{ + sepgsql_sid_t newSid; + Oid relOid = RelationGetRelid(rel); + Oid nspOid, tblOid; + char relkind; + + if (!HeapTupleHasSecid(tuple)) + return; + + /* initialize */ + newSid.relid = relOid; + newSid.secid = InvalidOid; + + switch (relOid) + { + case DatabaseRelationId: + /* should be never happen */ + elog(WARNING, "bug? pg_database tuple without security label"); + break; + + case NamespaceRelationId: + newSid = sepgsqlGetDefaultSchemaSecid(MyDatabaseId); + break; + + case RelationRelationId: + nspOid = ((Form_pg_class) GETSTRUCT(tuple))->relnamespace; + relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind; + + switch (relkind) + { + case RELKIND_RELATION: + newSid = sepgsqlGetDefaultTableSecid(nspOid); + break; + + case RELKIND_SEQUENCE: + newSid = sepgsqlGetDefaultSequenceSecid(nspOid); + break; + + default: + newSid = sepgsqlGetDefaultTupleSecid(relOid); + break; + } + break; + + case ProcedureRelationId: + nspOid = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace; + newSid = sepgsqlGetDefaultProcedureSecid(nspOid); + break; + + case AttributeRelationId: + tblOid = ((Form_pg_attribute) GETSTRUCT(tuple))->attrelid; + if (get_rel_relkind(tblOid) == RELKIND_RELATION) + newSid = sepgsqlGetDefaultColumnSecid(tblOid); + break; + + case LargeObjectMetadataRelationId: + newSid = sepgsqlGetDefaultBlobSecid(MyDatabaseId); + break; + + default: + newSid = sepgsqlGetDefaultTupleSecid(relOid); + break; + } + + HeapTupleSetSecid(tuple, newSid.secid); +} + +/* + * sepgsqlPostBootstrapingMode + * + * Assign initial security context + */ +static void +sepgsqlInitialLabeling(Oid relOid, char *seclabels[]) +{ + Relation rel; + HeapScanDesc scan; + HeapTuple tuple; + HeapTuple newtup; + + rel = heap_open(relOid, RowExclusiveLock); + + scan = heap_beginscan(rel, SnapshotNow, 0, NULL); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Oid secid = InvalidOid; + Oid attrelid; + char relkind; + + if (!HeapTupleHasSecid(tuple)) + continue; + + switch (relOid) + { + case DatabaseRelationId: + secid = securityRawSecLabelIn(relOid, seclabels[0]); + break; + + case NamespaceRelationId: + secid = securityRawSecLabelIn(relOid, seclabels[1]); + break; + + case RelationRelationId: + relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind; + switch (relkind) + { + case RELKIND_RELATION: + secid = securityRawSecLabelIn(relOid, seclabels[2]); + break; + case RELKIND_SEQUENCE: + secid = securityRawSecLabelIn(relOid, seclabels[3]); + break; + default: + secid = securityRawSecLabelIn(relOid, seclabels[6]); + break; + } + break; + + case AttributeRelationId: + attrelid = ((Form_pg_attribute) GETSTRUCT(tuple))->attrelid; + if (get_rel_relkind(attrelid) == RELKIND_RELATION) + secid = securityRawSecLabelIn(relOid, seclabels[5]); + break; + + case ProcedureRelationId: + secid = securityRawSecLabelIn(relOid, seclabels[4]); + break; + + case LargeObjectMetadataRelationId: + secid = securityRawSecLabelIn(relOid, seclabels[7]); + break; + + default: + secid = securityRawSecLabelIn(relOid, seclabels[6]); + break; + } + + /* + * Inplace update + */ + newtup = heap_copytuple(tuple); + + HeapTupleSetSecid(newtup, secid); + + heap_inplace_update(rel, newtup); + } + heap_endscan(scan); + + heap_close(rel, RowExclusiveLock); +} + +void +sepgsqlPostBootstrapingMode(void) +{ + Form_pg_class classForm; + Relation rel; + ScanKeyData skey; + HeapScanDesc scan; + HeapTuple tuple; + char *scontext; + char *seclabels[8]; + + /* + * sepgsqlIsEnabled() is not available because it always returns + * false in bootstraping mode + */ + Assert(IsBootstrapProcessingMode()); + if (sepostgresql_mode == SEPGSQL_MODE_DISABLED || + is_selinux_enabled() < 1) + return; + + /* + * Compute default initial security context + */ + if (getprevcon_raw(&scontext) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not obtain current context"))); + + seclabels[0] = sepgsqlComputeCreate(scontext, scontext, + SEPG_CLASS_DB_DATABASE); + seclabels[1] = sepgsqlComputeCreate(scontext, seclabels[0], + SEPG_CLASS_DB_SCHEMA); + seclabels[2] = sepgsqlComputeCreate(scontext, seclabels[1], + SEPG_CLASS_DB_TABLE); + seclabels[3] = sepgsqlComputeCreate(scontext, seclabels[1], + SEPG_CLASS_DB_SEQUENCE); + seclabels[4] = sepgsqlComputeCreate(scontext, seclabels[1], + SEPG_CLASS_DB_PROCEDURE); + seclabels[5] = sepgsqlComputeCreate(scontext, seclabels[2], + SEPG_CLASS_DB_COLUMN); + seclabels[6] = sepgsqlComputeCreate(scontext, seclabels[2], + SEPG_CLASS_DB_TUPLE); + seclabels[7] = sepgsqlComputeCreate(scontext, seclabels[0], + SEPG_CLASS_DB_BLOB); + /* + * Inplace update + */ + StartTransactionCommand(); + + rel = heap_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_class_relkind, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(RELKIND_RELATION)); + + scan = heap_beginscan(rel, SnapshotNow, 1, &skey); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + sepgsqlInitialLabeling(HeapTupleGetOid(tuple), seclabels); + + heap_endscan(scan); + + heap_close(rel, AccessShareLock); + + CommitTransactionCommand(); +} + +/* + * sepgsqlGetSysobjSecid + * + * It returns a pair of relid/secid for the given OID. + */ +static sepgsql_sid_t +getSysobjSecidDirect(Oid classOid, Oid indexOid, Oid objectId, uint16 *tclass) +{ + sepgsql_sid_t sid; + Relation rel; + HeapTuple tup; + ScanKeyData skey; + SysScanDesc scan; + + rel = heap_open(CastRelationId, AccessShareLock); + + ScanKeyInit(&skey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(rel, CastOidIndexId, true, + SnapshotNow, 1, &skey); + tup = systable_getnext(scan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "system object lookup failed for oid %u on relation %u", + objectId, classOid); + + sid = sepgsqlGetTupleSecid(classOid, tup, tclass); + + systable_endscan(scan); + + heap_close(rel, AccessShareLock); + + return sid; +} + +sepgsql_sid_t +sepgsqlGetSysobjSecid(Oid classOid, Oid objectId, int32 objsubId, uint16 *tclass) +{ + sepgsql_sid_t sid; + HeapTuple tup; + + switch (classOid) + { + case AccessMethodRelationId: + tup = SearchSysCache(AMOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for access method: %u", objectId); + break; + + case AccessMethodOperatorRelationId: + return getSysobjSecidDirect(AccessMethodOperatorRelationId, + AccessMethodOperatorOidIndexId, + objectId, tclass); + + case AccessMethodProcedureRelationId: + return getSysobjSecidDirect(AccessMethodProcedureRelationId, + AccessMethodProcedureOidIndexId, + objectId, tclass); + + case AuthIdRelationId: + tup = SearchSysCache(AUTHOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for role: %u", objectId); + break; + + case CastRelationId: + return getSysobjSecidDirect(CastRelationId, + CastOidIndexId, + objectId, tclass); + + case ConstraintRelationId: + tup = SearchSysCache(CONSTROID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for constraint: %u", objectId); + break; + + case ConversionRelationId: + tup = SearchSysCache(CONVOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for conversion: %u", objectId); + break; + + case DatabaseRelationId: + tup = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for database: %u", objectId); + break; + + case ForeignDataWrapperRelationId: + tup = SearchSysCache(FOREIGNDATAWRAPPEROID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for FDW: %u", objectId); + break; + + case ForeignServerRelationId: + tup = SearchSysCache(FOREIGNSERVEROID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for foreign server: %u", objectId); + break; + + case LanguageRelationId: + tup = SearchSysCache(LANGOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + break; + + case LargeObjectRelationId: + case LargeObjectMetadataRelationId: + { + Relation rel; + ScanKeyData skey; + SysScanDesc scan; + + rel = heap_open(LargeObjectMetadataRelationId, AccessShareLock); + + ScanKeyInit(&skey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(rel, LargeObjectMetadataOidIndexId, + true, SnapshotNow, 1, &skey); + + tup = systable_getnext(scan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "largeobject %u lookup failed", objectId); + + sid = sepgsqlGetTupleSecid(classOid, tup, tclass); + systable_endscan(scan); + + heap_close(rel, AccessShareLock); + } + return sid; + + case RelationRelationId: + if (objsubId != 0) + { + classOid = AttributeRelationId; + tup = SearchSysCache(ATTNUM, + ObjectIdGetDatum(objectId), + Int16GetDatum(objsubId), + 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + objsubId, objectId); + } + else + { + classOid = RelationRelationId; + tup = SearchSysCache(RELOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for relation %u", objectId); + } + break; + + case NamespaceRelationId: + tup = SearchSysCache(NAMESPACEOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for schema %u", objectId); + break; + + case OperatorClassRelationId: + tup = SearchSysCache(CLAOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for opclass %u", objectId); + break; + + case OperatorFamilyRelationId: + tup = SearchSysCache(OPFAMILYOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for opfamily %u", objectId); + break; + + case OperatorRelationId: + tup = SearchSysCache(OPEROID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for operator %u", objectId); + break; + + case ProcedureRelationId: + tup = SearchSysCache(PROCOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for procedure %u", objectId); + break; + + case RewriteRelationId: + return getSysobjSecidDirect(RewriteRelationId, + RewriteOidIndexId, + objectId, tclass); + + case TableSpaceRelationId: + return getSysobjSecidDirect(TableSpaceRelationId, + TablespaceOidIndexId, + objectId, tclass); + + case TriggerRelationId: + return getSysobjSecidDirect(TriggerRelationId, + TriggerOidIndexId, + objectId, tclass); + + case TSConfigRelationId: + tup = SearchSysCache(TSCONFIGOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for text search configuration %u", objectId); + break; + + case TSDictionaryRelationId: + tup = SearchSysCache(TSDICTOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for text search dictionary %u", objectId); + break; + + case TSParserRelationId: + tup = SearchSysCache(TSPARSEROID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for text search parser %u", objectId); + break; + + case TSTemplateRelationId: + tup = SearchSysCache(TSTEMPLATEOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for text search template %u", objectId); + break; + + case TypeRelationId: + tup = SearchSysCache(TYPEOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", objectId); + break; + + case UserMappingRelationId: + tup = SearchSysCache(USERMAPPINGOID, + ObjectIdGetDatum(objectId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for user mapping %u", objectId); + break; + + default: + elog(ERROR, "unexpected class OID: %u", classOid); + tup = NULL; /* for compiler quiet */ + break; + } + + Assert(HeapTupleIsValid(tup)); + + sid = sepgsqlGetTupleSecid(classOid, tup, tclass); + + ReleaseSysCache(tup); + + return sid; +} + +/* + * sepgsqlGetTupleSecid + * + * It returns a pair of relid/secid for the given HeapTuple. + * A few system catalogs is handled as an attribute of other + * system objects. + * E.g) pg_attrdef is an attribute of a certain pg_attribute + */ +sepgsql_sid_t +sepgsqlGetTupleSecid(Oid tableOid, HeapTuple tuple, uint16 *tclass) +{ + sepgsql_sid_t sid; + HeapTuple exttup; + Oid extid; + Oid extcls; + AttrNumber extsub; + + /* initialize (unlabeled security context) */ + sid.relid = tableOid; + sid.secid = InvalidOid; + if (tclass) + *tclass = SEPG_CLASS_DB_TUPLE; + + switch (tableOid) + { + case AggregateRelationId: + extid = ((Form_pg_aggregate) GETSTRUCT(tuple))->aggfnoid; + exttup = SearchSysCache(PROCOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(ProcedureRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case AccessMethodOperatorRelationId: + extid = ((Form_pg_amop) GETSTRUCT(tuple))->amopfamily; + exttup = SearchSysCache(OPFAMILYOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(OperatorFamilyRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case AccessMethodProcedureRelationId: + extid = ((Form_pg_amproc) GETSTRUCT(tuple))->amprocfamily; + exttup = SearchSysCache(OPFAMILYOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(OperatorFamilyRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case AttrDefaultRelationId: + extid = ((Form_pg_attrdef) GETSTRUCT(tuple))->adrelid; + extsub = ((Form_pg_attrdef) GETSTRUCT(tuple))->adnum; + exttup = SearchSysCache(ATTNUM, + ObjectIdGetDatum(extid), + Int16GetDatum(extsub), + 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(AttributeRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case AttributeRelationId: + extid = ((Form_pg_attribute) GETSTRUCT(tuple))->attrelid; + exttup = SearchSysCache(RELOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + char relkind = ((Form_pg_class) GETSTRUCT(exttup))->relkind; + + if (relkind == RELKIND_RELATION) + { + if (tclass) + *tclass = SEPG_CLASS_DB_COLUMN; + sid.secid = HeapTupleGetSecid(tuple); + } + else + sid = sepgsqlGetTupleSecid(RelationRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case AuthMemRelationId: + extid = ((Form_pg_auth_members) GETSTRUCT(tuple))->roleid; + exttup = SearchSysCache(AUTHOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(AuthIdRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case ConstraintRelationId: + /* CHECK constraint is an attribute of the relation */ + extid = ((Form_pg_constraint) GETSTRUCT(tuple))->conrelid; + if (OidIsValid(extid)) + { + exttup = SearchSysCache(RELOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(RelationRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + } + /* DOMAIN constraint is an attribute of the domain type */ + extid = ((Form_pg_constraint) GETSTRUCT(tuple))->contypid; + if (OidIsValid(extid)) + { + sid.relid = TypeRelationId; + exttup = SearchSysCache(TYPEOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(TypeRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + } + /* Database's context for global assertion */ + exttup = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(DatabaseRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case DatabaseRelationId: + sid.secid = HeapTupleGetSecid(tuple); + if (tclass) + *tclass = SEPG_CLASS_DB_DATABASE; + break; + + case DescriptionRelationId: + /* recursive call */ + extid = ((Form_pg_description) GETSTRUCT(tuple))->objoid; + extcls = ((Form_pg_description) GETSTRUCT(tuple))->classoid; + return sepgsqlGetSysobjSecid(extcls, extid, 0, tclass); + + case EnumRelationId: + extid = ((Form_pg_enum) GETSTRUCT(tuple))->enumtypid; + exttup = SearchSysCache(TYPEOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(TypeRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case IndexRelationId: + extid = ((Form_pg_index) GETSTRUCT(tuple))->indrelid; + exttup = SearchSysCache(RELOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(RelationRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case InheritsRelationId: + extid = ((Form_pg_inherits) GETSTRUCT(tuple))->inhrelid; + exttup = SearchSysCache(RELOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(RelationRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case LargeObjectRelationId: + extid = ((Form_pg_largeobject) GETSTRUCT(tuple))->loid; + extcls = LargeObjectMetadataRelationId; + return sepgsqlGetSysobjSecid(extcls, extid, 0, tclass); + + case LargeObjectMetadataRelationId: + sid.secid = HeapTupleGetSecid(tuple); + if (tclass) + *tclass = SEPG_CLASS_DB_BLOB; + break; + + case NamespaceRelationId: + sid.secid = HeapTupleGetSecid(tuple); + if (tclass) + *tclass = SEPG_CLASS_DB_SCHEMA; + break; + + case ProcedureRelationId: + sid.secid = HeapTupleGetSecid(tuple); + if (tclass) + *tclass = SEPG_CLASS_DB_PROCEDURE; + break; + + case RelationRelationId: + sid.secid = HeapTupleGetSecid(tuple); + if (tclass) + { + char relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind; + + switch (relkind) + { + case RELKIND_RELATION: + *tclass = SEPG_CLASS_DB_TABLE; + break; + + case RELKIND_SEQUENCE: + *tclass = SEPG_CLASS_DB_SEQUENCE; + break; + + default: + *tclass = SEPG_CLASS_DB_TUPLE; + break; + } + } + break; + + case RewriteRelationId: + extid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class; + exttup = SearchSysCache(RELOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(RelationRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case SharedDescriptionRelationId: + /* recursive invocation */ + extid = ((Form_pg_shdescription) GETSTRUCT(tuple))->objoid; + extcls = ((Form_pg_shdescription) GETSTRUCT(tuple))->classoid; + return sepgsqlGetSysobjSecid(extcls, extid, 0, tclass); + + case StatisticRelationId: + extid = ((Form_pg_statistic) GETSTRUCT(tuple))->starelid; + extsub = ((Form_pg_statistic) GETSTRUCT(tuple))->staattnum; + exttup = SearchSysCache(ATTNUM, + ObjectIdGetDatum(extid), + Int16GetDatum(extsub), + 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(AttributeRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case TriggerRelationId: + extid = ((Form_pg_trigger) GETSTRUCT(tuple))->tgrelid; + exttup = SearchSysCache(RELOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(RelationRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + case TSConfigMapRelationId: + extid = ((Form_pg_ts_config_map) GETSTRUCT(tuple))->mapcfg; + exttup = SearchSysCache(TSCONFIGOID, + ObjectIdGetDatum(extid), + 0, 0, 0); + if (HeapTupleIsValid(exttup)) + { + sid = sepgsqlGetTupleSecid(TSConfigRelationId, + exttup, tclass); + ReleaseSysCache(exttup); + } + break; + + default: + /* No external lookups (normal case) */ + sid.secid = HeapTupleGetSecid(tuple); + break; + } + + return sid; +} + +/* + * sepgsqlRawSecLabelIn + * correctness checks for the given security context + */ +char * +sepgsqlRawSecLabelIn(char *seclabel) +{ + if (!sepgsqlIsEnabled()) + return seclabel; + + if (!seclabel || security_check_context_raw(seclabel) < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_SECURITY_LABEL), + errmsg("Invalid security context: \"%s\"", seclabel))); + + return seclabel; +} + +/* + * sepgsqlRawSecLabelOut + * correctness checks for the given security context, + * and replace it if invalid security context + */ +char * +sepgsqlRawSecLabelOut(char *seclabel) +{ + if (!sepgsqlIsEnabled()) + return seclabel; + + if (!seclabel || security_check_context_raw(seclabel) < 0) + { + security_context_t unlabeledcon; + + if (security_get_initial_context_raw("unlabeled", + &unlabeledcon) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Unabled to get unlabeled security context"))); + PG_TRY(); + { + seclabel = pstrdup(unlabeledcon); + } + PG_CATCH(); + { + freecon(unlabeledcon); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(unlabeledcon); + } + return seclabel; +} + +/* + * sepgsqlTransSecLabelIn + * sepgsqlTransSecLabelOut + * translation between human-readable and raw format + */ +char * +sepgsqlTransSecLabelIn(char *seclabel) +{ + security_context_t rawlabel; + security_context_t result; + + if (!sepgsqlIsEnabled() || + !sepostgresql_mcstrans) + return seclabel; + + if (selinux_trans_to_raw_context(seclabel, &rawlabel) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: failed to translate \"%s\"", seclabel))); + PG_TRY(); + { + result = pstrdup(rawlabel); + } + PG_CATCH(); + { + freecon(rawlabel); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(rawlabel); + + return result; +} + +char * +sepgsqlTransSecLabelOut(char *seclabel) +{ + security_context_t translabel; + security_context_t result; + + if (!sepgsqlIsEnabled() || + !sepostgresql_mcstrans) + return seclabel; + + if (selinux_raw_to_trans_context(seclabel, &translabel) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: failed to translate \"%s\"", seclabel))); + PG_TRY(); + { + result = pstrdup(translabel); + } + PG_CATCH(); + { + freecon(translabel); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(translabel); + + return result; +} + +char * +sepgsqlSysattSecLabelOut(Oid relid, HeapTuple tuple) +{ + sepgsql_sid_t sid; + + sid = sepgsqlGetTupleSecid(relid, tuple, NULL); + + return securityTransSecLabelOut(sid.relid, sid.secid); +} diff --git a/src/backend/security/sepgsql/misc.c b/src/backend/security/sepgsql/misc.c new file mode 100644 index 0000000..2f7c466 --- /dev/null +++ b/src/backend/security/sepgsql/misc.c @@ -0,0 +1,214 @@ +/* + * src/backend/security/sepgsql/misc.c + * Miscellaneous facilities in SE-PostgreSQL + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" +#include "libpq/libpq-be.h" +#include "miscadmin.h" +#include "security/sepgsql.h" +#include "utils/builtins.h" + +/* + * SE-PostgreSQL specific functions + */ +Datum +sepgsql_getcon(PG_FUNCTION_ARGS) +{ + security_context_t context; + + if (!sepgsqlIsEnabled()) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux: disabled now"))); + + context = sepgsqlGetClientLabel(); + context = sepgsqlTransSecLabelOut(context); + return CStringGetTextDatum(context); +} + +Datum +sepgsql_server_getcon(PG_FUNCTION_ARGS) +{ + char *context; + + if (!sepgsqlIsEnabled()) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux: disabled now"))); + + context = sepgsqlGetServerLabel(); + context = sepgsqlTransSecLabelOut(context); + + return CStringGetTextDatum(context); +} + +/* + * sepgsql_(get|set)_(user|role|type|range) + * get/set a component of security context. + */ +static void +parse_security_context(security_context_t context, + char **user, char **role, char **type, char **range) +{ + security_context_t raw_context; + char *tok; + + if (!sepgsqlIsEnabled()) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELinux: disabled now"))); + + if (selinux_trans_to_raw_context(context, &raw_context) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not translate mls label: %s", context))); + + PG_TRY(); + { + tok = strtok(raw_context, ":"); + if (user) + *user = (!tok ? NULL : pstrdup(tok)); + + tok = strtok(NULL, ":"); + if (role) + *role = (!tok ? NULL : pstrdup(tok)); + + tok = strtok(NULL, ":"); + if (type) + *type = (!tok ? NULL : pstrdup(tok)); + + tok = strtok(NULL, "\0"); + if (range) + *range = (!tok ? NULL : pstrdup(tok)); + } + PG_CATCH(); + { + freecon(raw_context); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(raw_context); +} + +Datum +sepgsql_get_user(PG_FUNCTION_ARGS) +{ + security_context_t context = TextDatumGetCString(PG_GETARG_TEXT_P(0)); + char *user; + + parse_security_context(context, &user, NULL, NULL, NULL); + if (!user) + ereport(ERROR, + (errcode(ERRCODE_INVALID_SECURITY_LABEL), + errmsg("could not extract user of \"%s\"", context))); + + PG_RETURN_TEXT_P(CStringGetTextDatum(user)); +} + +Datum +sepgsql_get_role(PG_FUNCTION_ARGS) +{ + security_context_t context = TextDatumGetCString(PG_GETARG_TEXT_P(0)); + char *role; + + parse_security_context(context, NULL, &role, NULL, NULL); + if (!role) + ereport(ERROR, + (errcode(ERRCODE_INVALID_SECURITY_LABEL), + errmsg("could not extract role of \"%s\"", context))); + + PG_RETURN_TEXT_P(CStringGetTextDatum(role)); +} + +Datum +sepgsql_get_type(PG_FUNCTION_ARGS) +{ + security_context_t context = TextDatumGetCString(PG_GETARG_TEXT_P(0)); + char *type; + + parse_security_context(context, NULL, NULL, &type, NULL); + if (!type) + ereport(ERROR, + (errcode(ERRCODE_INVALID_SECURITY_LABEL), + errmsg("could not extract type of \"%s\"", context))); + + PG_RETURN_TEXT_P(CStringGetTextDatum(type)); +} + +Datum +sepgsql_get_range(PG_FUNCTION_ARGS) +{ + security_context_t context = TextDatumGetCString(PG_GETARG_TEXT_P(0)); + char *range; + + parse_security_context(context, NULL, NULL, NULL, &range); + if (!range) + ereport(ERROR, + (errcode(ERRCODE_INVALID_SECURITY_LABEL), + errmsg("could not extract range of \"%s\"", context))); + + PG_RETURN_TEXT_P(CStringGetTextDatum(range)); +} + +static Datum +sepgsql_set_common(char *context, + char *user, char *role, char *type, char *range) +{ + StringInfoData newcon; + + parse_security_context(context, + !user ? &user : NULL, + !role ? &role : NULL, + !type ? &type : NULL, + !range ? &range : NULL); + if (!user || !role || !type) + ereport(ERROR, + (errcode(ERRCODE_INVALID_SECURITY_LABEL), + errmsg("invalid security context: \"%s\"", context))); + + initStringInfo(&newcon); + appendStringInfo(&newcon, "%s:%s:%s", user, role, type); + if (range) + appendStringInfo(&newcon, ":%s", range); + + return CStringGetTextDatum(sepgsqlTransSecLabelOut(newcon.data)); +} + +Datum +sepgsql_set_user(PG_FUNCTION_ARGS) +{ + security_context_t context = TextDatumGetCString(PG_GETARG_TEXT_P(0)); + char *user = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + + return sepgsql_set_common(context, user, NULL, NULL, NULL); +} + +Datum +sepgsql_set_role(PG_FUNCTION_ARGS) +{ + security_context_t context = TextDatumGetCString(PG_GETARG_TEXT_P(0)); + char *role = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + + return sepgsql_set_common(context, NULL, role, NULL, NULL); +} + +Datum +sepgsql_set_type(PG_FUNCTION_ARGS) +{ + security_context_t context = TextDatumGetCString(PG_GETARG_TEXT_P(0)); + char *type = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + + return sepgsql_set_common(context, NULL, NULL, type, NULL); +} + +Datum +sepgsql_set_range(PG_FUNCTION_ARGS) +{ + security_context_t context = TextDatumGetCString(PG_GETARG_TEXT_P(0)); + char *range = TextDatumGetCString(PG_GETARG_TEXT_P(1)); + + return sepgsql_set_common(context, NULL, NULL, NULL, range); +} diff --git a/src/backend/security/sepgsql/perms.c b/src/backend/security/sepgsql/perms.c new file mode 100644 index 0000000..5943f40 --- /dev/null +++ b/src/backend/security/sepgsql/perms.c @@ -0,0 +1,597 @@ +/* + * src/backend/utils/sepgsql/perms.c + * SE-PostgreSQL permission checks + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "catalog/pg_database.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "security/sepgsql.h" +#include "utils/lsyscache.h" + +/* + * Dynamic object class/permissions mapping + * + * SELinux exports the list of object classes and permissions at + * /selinux/class. The libselinux provides an interface to translate + * between their names and codes. + */ +static struct +{ + const char *class_name; + security_class_t class_code; + struct + { + const char *perm_name; + access_vector_t perm_code; + } av[sizeof(access_vector_t) * 8]; +} selinux_catalog[] = { + { + "process", SEPG_CLASS_PROCESS, + { + {"translation", SEPG_PROCESS__TRANSITION }, + {NULL, 0} + } + }, + { + "file", SEPG_CLASS_FILE, + { + {"read", SEPG_FILE__READ }, + {"write", SEPG_FILE__WRITE }, + {"create", SEPG_FILE__CREATE }, + {"getattr", SEPG_FILE__GETATTR }, + {NULL, 0} + } + }, + { + "dir", SEPG_CLASS_DIR, + { + {"read", SEPG_DIR__READ }, + {"write", SEPG_DIR__WRITE }, + {"create", SEPG_DIR__CREATE }, + {"getattr", SEPG_DIR__GETATTR }, + {NULL,0} + } + }, + { + "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 }, + {NULL,0} + } + }, + { + "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 }, + {NULL,0} + } + }, + { + "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 }, + {NULL,0} + } + }, + { + "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 }, + {NULL,0} + } + }, + { + "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 }, + {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 }, + { "install_module", SEPG_DB_DATABASE__INSTALL_MODULE }, + { "load_module", SEPG_DB_DATABASE__LOAD_MODULE }, + { "superuser", SEPG_DB_DATABASE__SUPERUSER }, + { 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 }, + { "reference", SEPG_DB_TABLE__REFERENCE }, + { 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 }, + { "untrusted", SEPG_DB_PROCEDURE__UNTRUSTED }, + { 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 }, + { "reference", SEPG_DB_COLUMN__REFERENCE }, + { 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 }, + } + } +}; + +/* + * sepgsqlTransToExternalClass + * It translate the given class code (defined as SEPGCLASS_(class)) into + * external code which is necessary to communicate in-kernel SELinux + */ +extern security_class_t +sepgsqlTransToExternalClass(uint16 tclass) +{ + Assert(tclass < SEPG_CLASS_MAX); + + return string_to_security_class(selinux_catalog[tclass].class_name); +} + +/* + * sepgsqlTransToInternalPerms + * It translate the given permission masks into internal representation + * defined as SEPG_(class)_(permission). + */ +extern void +sepgsqlTransToInternalPerms(security_class_t tclass, struct av_decision *avd) +{ + security_class_t tclass_ex; + struct av_decision i_avd; + int i, deny_unknown; + + Assert(tclass < SEPG_CLASS_MAX); + + memset(&i_avd, 0, sizeof(struct av_decision)); + + deny_unknown = security_deny_unknown(); + + tclass_ex = sepgsqlTransToExternalClass(tclass); + for (i=0; selinux_catalog[tclass].av[i].perm_name; i++) + { + const char *perm_name = selinux_catalog[tclass].av[i].perm_name; + access_vector_t perm_code = selinux_catalog[tclass].av[i].perm_code; + access_vector_t perm_code_ex; + + perm_code_ex = string_to_av_perm(tclass_ex, perm_name); + if (!perm_code_ex) + { + /* fill up undefined permission */ + if (!deny_unknown) + i_avd.allowed |= perm_code; + i_avd.decided |= perm_code; + i_avd.auditdeny |= perm_code; + continue; + } + + if (avd->allowed & perm_code_ex) + i_avd.allowed |= perm_code; + if (avd->decided & perm_code_ex) + i_avd.decided |= perm_code; + if (avd->auditallow & perm_code_ex) + i_avd.auditallow |= perm_code; + if (avd->auditdeny & perm_code_ex) + i_avd.auditdeny |= perm_code; + } + + avd->allowed = i_avd.allowed; + avd->decided = i_avd.decided; + avd->auditallow = i_avd.auditallow; + avd->auditdeny = i_avd.auditdeny; +} + +/* + * sepgsqlGetClassString + * sepgsqlGetPermissionString + * It returns text representation of object classes/permissions + */ +const char * +sepgsqlGetClassString(uint16 tclass) +{ + Assert(tclass < SEPG_CLASS_MAX); + + return selinux_catalog[tclass].class_name; +} + +const char * +sepgsqlGetPermString(uint16 tclass, uint32 permission) +{ + int i; + + Assert(tclass < SEPG_CLASS_MAX); + + for (i=0; selinux_catalog[tclass].av[i].perm_name; i++) + { + if (selinux_catalog[tclass].av[i].perm_code == permission) + return selinux_catalog[tclass].av[i].perm_name; + } + return NULL; +} + +#if 0 + +/* + * sepgsqlFileObjectClass + * + * It returns proper object class of filesystem object already opened. + * It is necessary to check privileges voluntarily. + */ +uint16 +sepgsqlFileObjectClass(int fdesc) +{ + struct stat stbuf; + + if (fstat(fdesc, &stbuf) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file descriptor: %d", fdesc))); + + if (S_ISDIR(stbuf.st_mode)) + return SEPG_CLASS_DIR; + else if (S_ISCHR(stbuf.st_mode)) + return SEPG_CLASS_CHR_FILE; + else if (S_ISBLK(stbuf.st_mode)) + return SEPG_CLASS_BLK_FILE; + else if (S_ISFIFO(stbuf.st_mode)) + return SEPG_CLASS_FIFO_FILE; + else if (S_ISLNK(stbuf.st_mode)) + return SEPG_CLASS_LNK_FILE; + else if (S_ISSOCK(stbuf.st_mode)) + return SEPG_CLASS_SOCK_FILE; + + return SEPG_CLASS_FILE; +} + +/* + * sepgsqlTupleObjectClass + * + * It returns correct object class of given tuple + */ +uint16 +sepgsqlTupleObjectClass(Oid relid, HeapTuple tuple) +{ + Form_pg_class clsForm; + Form_pg_attribute attForm; + + switch (relid) + { + case DatabaseRelationId: + return SEPG_CLASS_DB_DATABASE; + + case NamespaceRelationId: + return SEPG_CLASS_DB_SCHEMA; + + case RelationRelationId: + clsForm = (Form_pg_class) GETSTRUCT(tuple); + if (clsForm->relkind == RELKIND_RELATION) + return SEPG_CLASS_DB_TABLE; + if (clsForm->relkind == RELKIND_SEQUENCE) + return SEPG_CLASS_DB_SEQUENCE; + break; + + case AttributeRelationId: + attForm = (Form_pg_attribute) GETSTRUCT(tuple); + if (IsBootstrapProcessingMode() && + (attForm->attrelid == TypeRelationId || + attForm->attrelid == ProcedureRelationId || + attForm->attrelid == AttributeRelationId || + attForm->attrelid == RelationRelationId)) + return SEPG_CLASS_DB_COLUMN; + + if (get_rel_relkind(attForm->attrelid) == RELKIND_RELATION) + return SEPG_CLASS_DB_COLUMN; + break; + + case ProcedureRelationId: + return SEPG_CLASS_DB_PROCEDURE; + + case LargeObjectRelationId: + return SEPG_CLASS_DB_BLOB; + } + return SEPG_CLASS_DB_TUPLE; +} + +/* + * sepgsqlTupleNamespace + * + * It returns an OID of the namespace, if the given system object is + * deployed under a certain namespace. + */ +Oid +sepgsqlTupleNamespace(Oid relOid, HeapTuple tuple) +{ + Oid nspOid; + + switch (relOid) + { + case RelationRelationId: + nspOid = ((Form_pg_class) GETSTRUCT(tuple))->relnamespace; + break; + + case ConstraintRelationId: + nspOid = ((Form_pg_constraint) GETSTRUCT(tuple))->connamespace; + break; + + case ConversionRelationId: + nspOid = ((Form_pg_conversion) GETSTRUCT(tuple))->connamespace; + break; + + case OperatorClassRelationId: + nspOid = ((Form_pg_opclass) GETSTRUCT(tuple))->opcnamespace; + break; + + case OperatorRelationId: + nspOid = ((Form_pg_operator) GETSTRUCT(tuple))->oprnamespace; + break; + + case OperatorFamilyRelationId: + nspOid = ((Form_pg_opfamily) GETSTRUCT(tuple))->opfnamespace; + break; + + case ProcedureRelationId: + nspOid = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace; + break; + + case TSConfigRelationId: + nspOid = ((Form_pg_ts_config) GETSTRUCT(tuple))->cfgnamespace; + break; + + case TSDictionaryRelationId: + nspOid = ((Form_pg_ts_dict) GETSTRUCT(tuple))->dictnamespace; + break; + + case TSParserRelationId: + nspOid = ((Form_pg_ts_parser) GETSTRUCT(tuple))->prsnamespace; + break; + + case TSTemplateRelationId: + nspOid = ((Form_pg_ts_template) GETSTRUCT(tuple))->tmplnamespace; + break; + + default: + /* no specific namespace */ + nspOid = InvalidOid; + break; + } + + return nspOid; +} + +/* + * sepgsqlTupleAuditName + * + * It returns an OID of the namespace, if the given system object is + * deployed under a certain namespace. + */ +void +sepgsqlTupleAuditName(Oid relid, HeapTuple tuple, char *auname_buf) +{ + char *name; + Oid extid; + + switch (relid) + { + case AccessMethodRelationId: + name = NameStr(((Form_pg_am) GETSTRUCT(tuple))->amname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case AttributeRelationId: + name = NameStr(((Form_pg_attribute) GETSTRUCT(tuple))->attname); + extid = ((Form_pg_attribute) GETSTRUCT(tuple))->attrelid; + sprintf(audit_name, "%s.%s", name, extid); + return; + + case AuthIdRelationId: + name = NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case ConversionRelationId: + name = NameStr(((Form_pg_conversion) GETSTRUCT(tuple))->conname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case DatabaseRelationId: + name = NameStr(((Form_pg_database) GETSTRUCT(tuple))->datname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case ForeignDataWrapperRelationId: + name = NameStr(((Form_pg_foreign_data_wrapper) GETSTRUCT(tuple))->fdwname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case ForeignServerRelationId: + name = NameStr(((Form_pg_foreign_server) GETSTRUCT(tuple))->srvname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case LanguageRelationId: + name = NameStr(((Form_pg_language) GETSTRUCT(tuple))->lanname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case NamespaceRelationId: + name = NameStr(((Form_pg_namespace) GETSTRUCT(tuple))->nspname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case OperatorClassRelationId: + name = NameStr(((Form_pg_opclass) GETSTRUCT(tuple))->opcname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case OperatorRelationId: + name = NameStr(((Form_pg_operator) GETSTRUCT(tuple))->oprname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case OperatorFamilyRelationId: + name = NameStr(((Form_pg_opfamily) GETSTRUCT(tuple))->opfname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case ProcedureRelationId: + name = NameStr(((Form_pg_proc) GETSTRUCT(tuple))->proname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case RelationRelationId: + name = NameStr(((Form_pg_class) GETSTRUCT(tuple))->relname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case TableSpaceRelationId: + name = NameStr(((Form_pg_tablespace) GETSTRUCT(tuple))->spcname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case TSConfigRelationId: + name = NameStr(((Form_pg_ts_config) GETSTRUCT(tuple))->cfgname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case TSDictionaryRelationId: + name = NameStr(((Form_pg_ts_dict) GETSTRUCT(tuple))->dictname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case TSParserRelationId: + name = NameStr(((Form_pg_ts_parser) GETSTRUCT(tuple))->prsname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + case TSTemplateRelationId: + name = NameStr(((Form_pg_templace) GETSTRUCT(tuple))->tmplname); + strncpy(auname_buf, name, NAMEDATALEN); + break; + + default: + /* no auditable name */ + auname_buf[0] = '\0'; + break; + } +} +#endif diff --git a/src/backend/security/sepgsql/policy/Makefile b/src/backend/security/sepgsql/policy/Makefile new file mode 100644 index 0000000..fc71b0c --- /dev/null +++ b/src/backend/security/sepgsql/policy/Makefile @@ -0,0 +1,28 @@ +# +# Makefile for SE-PostgreSQL security policy module +# +top_builddir = ../../../../.. +include $(top_builddir)/src/Makefile.global + +POLICY_BASEDIR := $(DESTDIR)/usr/share/selinux +POLICY_MAKEFILE := $(POLICY_BASEDIR)/devel/Makefile +POLICY_INSTDIR := $(POLICY_BASEDIR)/packages +PREFIX_RULE := "s/%%__prefix__%%/$(shell echo $(prefix)|sed 's/\//\\\//g')/g" +BINDIR_RULE := "s/%%__bindir__%%/$(shell echo $(bindir)|sed 's/\//\\\//g')/g" +LIBDIR_RULE := "s/%%__libdir__%%/$(shell echo $(pkglibdir)|sed 's/\//\\\//g')/g" + +all: sepostgresql-devel.pp + +install: all + test -d $(POLICY_INSTDIR) || mkdir -p $(POLICY_INSTDIR) + install -p -m 0644 sepostgresql-devel.pp $(POLICY_INSTDIR) + +sepostgresql-devel.pp: sepostgresql-devel.te sepostgresql-devel.fc + $(MAKE) -f $(POLICY_MAKEFILE) + +sepostgresql-devel.fc: sepostgresql-devel.fc.template + cat $< | sed -e $(PREFIX_RULE) -e $(BINDIR_RULE) -e $(LIBDIR_RULE) > $@ + +clean: + $(MAKE) -f $(POLICY_MAKEFILE) clean + rm -f *.fc diff --git a/src/backend/security/sepgsql/policy/sepostgresql-devel.fc.template b/src/backend/security/sepgsql/policy/sepostgresql-devel.fc.template new file mode 100644 index 0000000..380ada4 --- /dev/null +++ b/src/backend/security/sepgsql/policy/sepostgresql-devel.fc.template @@ -0,0 +1,12 @@ +# +# SE-PostgreSQL install path +# +%%__prefix__%%(/.*)? -- gen_context(system_u:object_r:usr_t,s0) + +%%__bindir__%%/(se)?postgres -- gen_context(system_u:object_r:postgresql_exec_t,s0) +%%__bindir__%%/(se)?pg_ctl -- gen_context(system_u:object_r:initrc_exec_t,s0) +%%__bindir__%%/initdb(\.sepgsql)? -- gen_context(system_u:object_r:postgresql_exec_t,s0) +%%__bindir__%%(/.*)? -- gen_context(system_u:object_r:bin_t,s0) + +%%__libdir__%%(/.*)? -- gen_context(system_u:object_r:lib_t,s0) + diff --git a/src/backend/security/sepgsql/policy/sepostgresql-devel.te b/src/backend/security/sepgsql/policy/sepostgresql-devel.te new file mode 100644 index 0000000..4b960a8 --- /dev/null +++ b/src/backend/security/sepgsql/policy/sepostgresql-devel.te @@ -0,0 +1,123 @@ +policy_module(sepostgresql-devel, 3.29) + +gen_require(` + class db_database all_db_database_perms; + class db_table all_db_table_perms; + class db_procedure all_db_procedure_perms; + class db_column all_db_column_perms; + class db_tuple all_db_tuple_perms; + class db_blob all_db_blob_perms; + + attribute sepgsql_client_type; + attribute sepgsql_unconfined_type; + + attribute sepgsql_database_type; + attribute sepgsql_table_type; + attribute sepgsql_sysobj_table_type; + attribute sepgsql_procedure_type; + attribute sepgsql_blob_type; + attribute sepgsql_module_type; + + # for regression test + type bin_t; + type user_home_t; + type sepgsql_trusted_proc_exec_t; + + attribute tmpfile; +') + +################################# +# +# Domain for Testcases +# + +role sepgsql_test_r; + +userdom_unpriv_user_template(sepgsql_test) +postgresql_role(sepgsql_test_r, sepgsql_test_t) + +allow sepgsql_test_t tmpfile : dir search_dir_perms; +allow sepgsql_test_t tmpfile : file rw_file_perms; + +optional_policy(` + term_write_all_terms(sepgsql_test_t) +') + +optional_policy(` + gen_require(` + type unconfined_t; + role unconfined_r; + ') + + tunable_policy(`sepgsql_regression_test_mode',` + allow unconfined_t sepgsql_test_t : process transition; + ') + unconfined_rw_pipes(sepgsql_test_t) + role unconfined_r types sepgsql_test_t; + role unconfined_r types sepgsql_trusted_proc_t; +') + +################################# +# +# SE-PostgreSQL Declarations +# + +## +##

+## Allow to generate auditallow logs +##

+##
+gen_tunable(sepgsql_enable_auditallow, false) + +## +##

+## Allow to generate auditdeny logs +##

+##
+gen_tunable(sepgsql_enable_auditdeny, true) + +## +##

+## Allow widespread permissions for regression test +## Don't set TRUE on operation phase +##

+##
+gen_tunable(sepgsql_regression_test_mode, false) + +######################################## +# +# SE-PostgreSQL audit switch for debugging +# +tunable_policy(`sepgsql_enable_auditallow',` + auditallow domain sepgsql_database_type : db_database *; + auditallow domain sepgsql_table_type : db_table *; + auditallow domain sepgsql_table_type : db_column *; + auditallow domain sepgsql_table_type : db_tuple { relabelfrom relabelto }; + auditallow domain sepgsql_sysobj_table_type : db_tuple *; + auditallow domain sepgsql_procedure_type : db_procedure *; + auditallow domain sepgsql_blob_type : db_blob *; + auditallow domain sepgsql_module_type : db_database { install_module }; + auditallow sepgsql_database_type sepgsql_module_type : db_database { load_module }; +') + +tunable_policy(`! sepgsql_enable_auditdeny',` + dontaudit domain sepgsql_database_type : db_database *; + dontaudit domain sepgsql_table_type : db_table *; + dontaudit domain sepgsql_table_type : db_column *; + dontaudit domain sepgsql_table_type : db_tuple { relabelfrom relabelto }; + dontaudit domain sepgsql_sysobj_table_type : db_tuple *; + dontaudit domain sepgsql_procedure_type : db_procedure *; + dontaudit domain sepgsql_blob_type : db_blob *; + dontaudit domain sepgsql_module_type : db_database { install_module }; + dontaudit sepgsql_database_type sepgsql_module_type : db_database { load_module }; +') + +######################################## +# +# SE-PostgreSQL regression test mode switch +# +tunable_policy(`sepgsql_regression_test_mode',` + allow sepgsql_client_type user_home_t : db_database { install_module }; + allow sepgsql_unconfined_type user_home_t : db_database { install_module }; + allow sepgsql_database_type user_home_t : db_database { load_module }; +') diff --git a/src/backend/security/sepgsql/selinux.c b/src/backend/security/sepgsql/selinux.c new file mode 100644 index 0000000..16f50e0 --- /dev/null +++ b/src/backend/security/sepgsql/selinux.c @@ -0,0 +1,1305 @@ +/* + * src/backend/security/sepgsql/selinux.c + * Routines to communicate with SELinux. + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "access/hash.h" +#include "access/xact.h" +#include "catalog/pg_security.h" +#include "lib/stringinfo.h" +#include "libpq/libpq-be.h" +#include "libpq/pqsignal.h" +#include "miscadmin.h" +#include "security/sepgsql.h" +#include "storage/fd.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + +#include +#include +#include + +/* + * selinux_catalog + * + * This static translation lookup table enables to associate a certain + * object class/permission name with its internal code, such as + * SEPG_CLASS_DB_SCHEMA. + * + * SELinux requires applications to represent object class and a set of + * permissions in code, instead of its name, when we ask SELinux's decision. + * + * See the definition of security_compute_av(3) API in libselinux. + * We need to gives a code of object class, and interpret what permissions + * are allowed on the object class from av_decision structure. + * Actual values of the code depend on the security policy. In other words, + * we cannot know what number is assigned on a certain object class and + * permissions. + * The string_to_security_class(3) and string_to_av_perm(3) APIs takes + * arguments with the name of object class/permission, and returns the + * code for the given object class/permissions. + * For example, we can know what code is assigned on the "db_table" class + * using these functions as follows: + * + * uint16 tclass_ex = string_to_security_class("db_table"); + * + * On the other hand, we use an alternative code internally to simplify + * the implementation, such as SEPG_CLASS_* for object class. + * The following selinux_catalog is used to translate the 'internal' + * code and the 'external' code. + * + * It allows to lookup name of the object class or permission corresponding + * to a certain 'internal' code. Then, we can give the name to SELinux's + * API to obtain 'external' code which can be used to ask in-kernel SELinux. + */ +static struct +{ + const char *class_name; + uint16 class_code; + struct + { + const char *perm_name; + uint32 perm_code; + } perms[32]; +} selinux_catalog[] = { + { + "process", SEPG_CLASS_PROCESS, + { + {"translation", SEPG_PROCESS__TRANSITION }, + {NULL, 0} + } + }, + { + "file", SEPG_CLASS_FILE, + { + {"read", SEPG_FILE__READ }, + {"write", SEPG_FILE__WRITE }, + {"create", SEPG_FILE__CREATE }, + {"getattr", SEPG_FILE__GETATTR }, + {NULL, 0} + } + }, + { + "dir", SEPG_CLASS_DIR, + { + {"read", SEPG_DIR__READ }, + {"write", SEPG_DIR__WRITE }, + {"create", SEPG_DIR__CREATE }, + {"getattr", SEPG_DIR__GETATTR }, + {NULL,0} + } + }, + { + "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 }, + {NULL,0} + } + }, + { + "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 }, + {NULL,0} + } + }, + { + "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 }, + {NULL,0} + } + }, + { + "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 }, + {NULL,0} + } + }, + { + "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 }, + {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_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 }, + { "usage", SEPG_DB_VIEW__USAGE }, + { 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 }, + } + } +}; + +/* + * GUC option: sepostgresql = [default|enforcing|permissive|disabled] + * + * SEPGSQL_MODE_DEFAULT : It follows system setting + * SEPGSQL_MODE_ENFORCING : Use enforcing mode always + * SEPGSQL_MODE_PERMISSIVE : Use permissive mode always + * SEPGSQL_MODE_INTERNAL : Internally used mode. Same as permissive mode + * except for silence in audit logs + * SEPGSQL_MODE_DISABLED : It always disables SE-PgSQL configuration + */ +int sepostgresql_mode; + +/* + * userspace access vector cache + * + * It enables to cache access control decisions in userspace, and minimize + * the number of system call invocations. + */ +static MemoryContext AvcMemCtx = NULL; + +#define AVC_HASH_NUM_SLOTS 256 +#define AVC_HASH_NUM_NODES 180 + +typedef struct _avc_datum +{ + uint32 hash_key; + + uint16 tclass; + sepgsql_sid_t tsid; + sepgsql_sid_t nsid; + char *tcontext; + char *ncontext; + + uint32 allowed; + uint32 auditallow; + uint32 auditdeny; + bool permissive; + + bool hot_cache; +} avc_datum; + +typedef struct _avc_page +{ + struct _avc_page *next; + + List *slot[AVC_HASH_NUM_SLOTS]; + + uint32 avc_count; + uint32 lru_hint; + + char scontext[1]; +} avc_page; + +static avc_page *current_page = NULL; + +static int avc_version; + +/* + * selinux_state + * + * It is deployed on the shared memory region, to show the system + * state of SELinux and its security policy. + * + * The selinux_state->version should be checked prior to avc accesses. + * If it does not match with the local avc_version, it means that + * system security policy was reloaded or system state (enforcing + * or permissive) was changed. + * + * The state monitoring worker process receives messages from the + * kernel using libselinux, and it updates the selinux_state. + */ +struct +{ + int version; + + bool enforcing; +} *selinux_state = NULL; + +/* + * sepgsqlShmemSize + * + * It returns required size for shared memory segment + */ +Size +sepgsqlShmemSize(void) +{ + if (!sepgsqlIsEnabled()) + return 0; + + return sizeof(*selinux_state); +} + +/* + * sepgsqlShmemInit + * + * It attaches shared memory segment. + */ +static void +sepgsqlShmemInit(void) +{ + bool found; + + selinux_state = ShmemInitStruct("SELinux system state", + sepgsqlShmemSize(), &found); + if (!found) + { + LWLockAcquire(SepgsqlAvcLock, LW_EXCLUSIVE); + + selinux_state->version = 0; + selinux_state->enforcing = (security_getenforce() > 0); + + LWLockRelease(SepgsqlAvcLock); + } +} + +/* + * sepgsqlIsEnabled + * sepgsqlIsEnabledBootstrap + * + * If it returns true, SE-PgSQL is enabled. Otherwise, it is disabled. + */ +bool +sepgsqlIsEnabledBootstrap(void) +{ + static int enabled = -1; + + /* + * If sepostgresql = off, it is always disabled. + */ + if (sepostgresql_mode == SEPGSQL_MODE_DISABLED) + return false; + + /* + * SE-PgSQL needs SELinux is enabled on the operating system. + * If it is disabled, SE-PgSQL has to be also disabled, even if + * 'enforcing' or 'permissive' are specified. + */ + if (enabled < 0) + enabled = is_selinux_enabled(); + + return enabled > 0 ? true : false; +} + +bool +sepgsqlIsEnabled(void) +{ + /* + * SE-PgSQL is not ready in bootstraping mode, + * except for initial labeling process + */ + if (IsBootstrapProcessingMode()) + return false; + + return sepgsqlIsEnabledBootstrap(); +} + +/* + * sepgsqlGetEnforce + * + * It returns true, if SE-PgSQL performs in enforcing mode. + * + * In enforcing mode, SE-PgSQL performs as expected. It checks permissions + * on the required action, and it prevents them if violated. + * In permissive mode, SE-PgSQL also checks permissions, but it does not + * prevent anything, even if violated. It generates audit logs for access + * violations, so we can use this mode to debug security policy itself. + */ +bool +sepgsqlGetEnforce(void) +{ + if (sepostgresql_mode == SEPGSQL_MODE_DEFAULT) + { + bool rc; + + LWLockAcquire(SepgsqlAvcLock, LW_SHARED); + rc = selinux_state->enforcing; + LWLockRelease(SepgsqlAvcLock); + + return rc; + } + else if (sepostgresql_mode == SEPGSQL_MODE_ENFORCING) + return true; + + return false; +} + +/* + * sepgsqlShowMode + * + * It returns the current performing mode ('selinux_support') + * in human readable form. + */ +char * +sepgsqlShowMode(void) +{ + if (!sepgsqlIsEnabled()) + return "disabled"; + + if (!sepgsqlGetEnforce()) + return "permissive"; + + return "enforcing"; +} + +/* + * sepgsqlGetClientLabel + * sepgsqlSetClientLabel + * sepgsqlGetServerLabel + */ +static char *clientLabel = NULL; + +char * +sepgsqlGetClientLabel(void) +{ + if (clientLabel) + return clientLabel; + + if (!MyProcPort) + { + /* + * When this server process was launched in single-user mode, + * it does not have any client socket, and the server process also + * performs as a client in same time. So, we apply a security context + * of the current process as a client's one. + * The getcon_raw(3) is an libselinux API to obtain security context + * of the current process in raw format. + */ + if (getprevcon_raw(&clientLabel) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not get server's security context"))); + } + else + { + /* + * Otherwise, SE-PgSQL obtains the security context of the client + * process using getpeercon(3). It is an API of SELinux to obtain + * the security context of the peer process for the given file + * descriptor of the client socket. + * For example, a process labeled as "system_u:system_r:httpd_t:s0" + * (which is typically apache/httpd) connect to the PgSQL server, + * getpeercon_raw() in server side returns the security context + * in client side. + * If MyProcPort->sock came from unix domain socket, we don't need + * any special configuration. OS handles them correctly. + * If it is tcp/ip socket, either labeled ipsec or static fallback + * context should be configured. + * The labeled ipsec is a feature to deliver the security context + * of remote peer processes with an enhancement of key exchange + * server (racoon). If SELinux is also available in the client host + * also, it is the most preferable option. + * The static fallback context is a feature to assign an alternative + * security context based on the source address and network device + * in usage. It can be applied, even if Windows is run on the client. + */ + if (getpeercon_raw(MyProcPort->sock, &clientLabel) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not get client's security context"))); + } + return clientLabel; +} + +char * +sepgsqlSetClientLabel(char *new_label) +{ + char *old_label = clientLabel; + avc_page *new_page; + int i, length; + + /* + * (1) Set new security context + */ + clientLabel = new_label; + + /* + * (2) Switch current AVC page + */ + if (current_page) + { + new_page = current_page; + do { + if (strcmp(new_page->scontext, new_label) == 0) + { + current_page = new_page; + return old_label; + } + new_page = new_page->next; + } while (new_page != current_page); + } + + /* Not found, create a new avc_page */ + length = sizeof(avc_page) + strlen(new_label); + new_page = MemoryContextAllocZero(AvcMemCtx, length); + + strcpy(new_page->scontext, new_label); + for (i=0; i < AVC_HASH_NUM_SLOTS; i++) + new_page->slot[i] = NIL; + + if (!current_page) + new_page->next = new_page; + else + { + new_page->next = current_page->next; + current_page->next = new_page; + } + + current_page = new_page; + + /* return old label */ + return old_label; +} + +char * +sepgsqlGetServerLabel(void) +{ + static char *serverLabel = NULL; + + if (!serverLabel) + { + if (getcon_raw(&serverLabel) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not get server's security context"))); + } + return serverLabel; +} + +/* + * sepgsqlAuditLog + * + * 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. + */ +static void +sepgsqlAuditLog(bool denied, char *scontext, char *tcontext, + uint16 tclass, uint32 audited, const char *audit_name) +{ + //static int auditfd = -2; + StringInfoData buf; + const char *tclass_name; + const char *perm_name; + int i; + + /* + * translation of security contexts to human readable format, + * if sepgsql_mcstrans is turned on. + */ + scontext = sepgsqlTransSecLabelOut(scontext); + tcontext = sepgsqlTransSecLabelOut(tcontext); + + /* lookup name of the object class */ + tclass_name = selinux_catalog[tclass].class_name; + + /* lookup name of the permissions */ + initStringInfo(&buf); + appendStringInfo(&buf, "{"); + + for (i=0; selinux_catalog[tclass].perms[i].perm_name; i++) + { + if (audited & (1UL << i)) + { + perm_name = selinux_catalog[tclass].perms[i].perm_name; + appendStringInfo(&buf, " %s", perm_name); + } + } + appendStringInfo(&buf, " }"); + + appendStringInfo(&buf, " scontext=%s tcontext=%s tclass=%s", + scontext, tcontext, tclass_name); + if (audit_name) + appendStringInfo(&buf, " name=%s", audit_name); + + ereport(LOG, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: %s %s", + (denied ? "denied" : "allowed"), buf.data))); +} + +/* + * computePermsInternal + * + * 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___ + * to suggest a set of allowed actions in this object class. + */ +static void +computePermsInternal(char *scontext, 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 = (deny_unknown > 0 ? 0 : ~0UL); + avd->auditallow = 0UL; + avd->auditdeny = ~0UL; + 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(scontext, 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].perms[i].perm_name; i++) + { + access_vector_t perm_code_ex; + const char *perm_name = selinux_catalog[tclass].perms[i].perm_name; + uint32 perm_code = selinux_catalog[tclass].perms[i].perm_code; + + perm_code_ex = string_to_av_perm(tclass_ex, perm_name); + if (perm_code_ex == 0) + { + /* fill up undefined permissions */ + if (!deny_unknown) + avd->allowed |= perm_code; + avd->auditdeny |= perm_code; + + continue; + } + + if (avd_ex.allowed & perm_code_ex) + avd->allowed |= perm_code; + if (avd_ex.auditallow & perm_code_ex) + avd->auditallow |= perm_code; + if (avd_ex.auditdeny & perm_code_ex) + avd->auditdeny |= perm_code; + } + + return; +} + +/* + * sepgsqlComputePerms + * + * It makes access control decision communicating with SELinux. + * If SELinux does not allow required permissions on a pair of the security + * contexts, it raises an error or returns false. + * + * scontext : The security context of subject. In most cases, it is client. + * tcontext : The security context of target database object. + * tclass : One of the object class code (SEPG_CLASS_*) declared in the + * header file. + * required : A bitmap of the required permissions (SEPG___) + * declared in the header file. + * audit_name : A human readable name of the database object for auditing. + * abort : True, if caller want to raise an error on access violation. + */ +extern bool +sepgsqlComputePerms(char *scontext, char *tcontext, + uint16 tclass, uint32 required, + const char *audit_name, bool abort) +{ + struct av_decision avd; + uint32 denied; + uint32 audited; + + computePermsInternal(scontext, tcontext, tclass, &avd); + + /* + * It logs a security audit record for the given request, if necessary. + * When SE-PgSQL performs 'internal' mode, it needs to keep silent. + */ + denied = required & ~avd.allowed; + audited = denied ? (denied & avd.auditdeny) + : (required & avd.auditallow); + + if (audited && sepostgresql_mode != SEPGSQL_MODE_INTERNAL) + { + sepgsqlAuditLog(!!denied, scontext, tcontext, + tclass, audited, audit_name); + } + + /* + * If here is no policy violations, or SE-PgSQL performs in permissive + * mode, or the client process peforms in permissive domain, it returns + * normally with 'true'. + */ + if (!denied || + !sepgsqlGetEnforce() || + (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) != 0) + return true; + + /* + * Otherwise, it raises an error or returns 'false', depending on the + * caller's indication by 'abort'. + */ + if (abort) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: security policy violation"))); + + return false; +} + +/* + * sepgsqlComputeCreate + * + * 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 : The security context of subject. In most cases, it is client. + * tcontext : The security context of the parent database object.. + * tclass : One of the object class code (SEPG_CLASS_*) declared in the + * header file. + */ +char * +sepgsqlComputeCreate(char *scontext, 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); + Assert(tclass == selinux_catalog[tclass].class_code); + + 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(scontext, tcontext, + tclass_ex, &ncontext)) + 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; +} + +/* + * sepgsqlAvcReset + * + * Invalidate all the cached access control decision + */ +static void +sepgsqlAvcReset(void) +{ + Assert(AvcMemCtx != NULL); + + MemoryContextReset(AvcMemCtx); + + current_page = NULL; + + sepgsqlSetClientLabel(sepgsqlGetClientLabel()); +} + +static void +sepgsqlAvcResetOnAbort(XactEvent event, void *arg) +{ + if (event == XACT_EVENT_ABORT) + sepgsqlAvcReset(); +} + +static void +sepgsqlAvcResetOnSubAbort(SubXactEvent event, SubTransactionId mySubid, + SubTransactionId parentSubid, void *arg) +{ + if (event == SUBXACT_EVENT_ABORT_SUB) + sepgsqlAvcReset(); +} + +/* + * sepgsqlAvcCheckValid + * + * It checks whether the current AVC pages are valid, or not. + */ +static bool +sepgsqlAvcCheckValid(void) +{ + bool result = true; + + LWLockAcquire(SepgsqlAvcLock, LW_SHARED); + if (avc_version != selinux_state->version) + { + sepgsqlAvcReset(); + + /* Copy the current version to local */ + avc_version = selinux_state->version; + + result = false; + } + LWLockRelease(SepgsqlAvcLock); + + return result; +} + +/* + * sepgsqlAvcReclaim + * + * It wipes recently unused AVC entries, if necessary. + */ +static void +sepgsqlAvcReclaim(avc_page *page) +{ + ListCell *l; + avc_datum *cache; + + while (page->avc_count > AVC_HASH_NUM_NODES - 10) + { + foreach (l, page->slot[page->lru_hint]) + { + cache = lfirst(l); + + if (cache->hot_cache) + cache->hot_cache = false; + else + { + list_delete_ptr(page->slot[page->lru_hint], cache); + pfree(cache); + page->avc_count--; + } + } + page->lru_hint = (page->lru_hint + 1) % AVC_HASH_NUM_SLOTS; + } +} + +/* + * sepgsqlAvcMakeEntry + * + * It makes a new avc entry, and insert it to the given page. + */ +#define avc_hash_key(trelid, tsecid, tclass, nrelid) \ + (hash_uint32((trelid) ^ (tsecid) ^ ((tclass) << 3) ^ (nrelid))) + +static avc_datum * +sepgsqlAvcMakeEntry(avc_page *page, sepgsql_sid_t tsid, uint16 tclass, Oid nrelid) +{ + MemoryContext oldctx; + char *scontext; + char *tcontext; + char *ncontext; + avc_datum *cache; + uint32 hash_key, index; + + hash_key = avc_hash_key(tsid.relid, tsid.secid, tclass, nrelid); + index = hash_key % AVC_HASH_NUM_SLOTS; + + oldctx = MemoryContextSwitchTo(AvcMemCtx); + + scontext = page->scontext; + tcontext = securityRawSecLabelOut(tsid.relid, tsid.secid); + ncontext = sepgsqlComputeCreate(scontext, tcontext, tclass); + + cache = palloc0(sizeof(avc_datum)); + + cache->hash_key = hash_key; + + cache->tclass = tclass; + + cache->hot_cache = true; + cache->tcontext = tcontext; + cache->ncontext = ncontext; + cache->tsid.relid = tsid.relid; + cache->tsid.secid = tsid.secid; + cache->nsid.relid = nrelid; + + if (OidIsValid(nrelid)) + cache->nsid.secid = securityRawSecLabelIn(nrelid, ncontext); + else + cache->nsid.secid = InvalidOid; + + if (!OidIsValid(nrelid)) + { + struct av_decision avd; + + computePermsInternal(scontext, tcontext, tclass, &avd); + cache->allowed = avd.allowed; + cache->auditallow = avd.auditallow; + cache->auditdeny = avd.auditdeny; + + if (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) + cache->permissive = true; + } + + if (page->avc_count > AVC_HASH_NUM_NODES) + sepgsqlAvcReclaim(page); + + page->slot[index] = lcons(cache, page->slot[index]); + page->avc_count++; + + MemoryContextSwitchTo(oldctx); + + return cache; +} + +/* + * sepgsqlAvcLookup + * + * It lookups required AVC entry + */ +static avc_datum * +sepgsqlAvcLookup(avc_page *page, sepgsql_sid_t tsid, uint16 tclass, Oid nrelid) +{ + avc_datum *cache = NULL; + uint32 hash_key, index; + ListCell *l; + + hash_key = avc_hash_key(tsid.relid, tsid.secid, tclass, nrelid); + index = hash_key % AVC_HASH_NUM_SLOTS; + + foreach (l, page->slot[index]) + { + cache = lfirst(l); + if (cache->hash_key == hash_key && + cache->tclass == tclass && + cache->tsid.relid == tsid.relid && + cache->tsid.secid == tsid.secid && + cache->nsid.relid == nrelid) + { + cache->hot_cache = true; + return cache; + } + } + return NULL; +} + +/* + * sepgsqlClientHasPerms + * + * It checks client's privileges on the given object using avc. + */ +bool +sepgsqlClientHasPerms(sepgsql_sid_t tsid, + uint16 tclass, uint32 required, + const char *audit_name, bool abort) +{ + avc_datum *cache; + uint32 denied, audited; + bool result = true; + + do { + cache = sepgsqlAvcLookup(current_page, tsid, tclass, InvalidOid); + if (!cache) + cache = sepgsqlAvcMakeEntry(current_page, tsid, tclass, InvalidOid); + } while (!sepgsqlAvcCheckValid()); + + denied = required & ~cache->allowed; + audited = denied ? (denied & cache->auditdeny) + : (required & cache->auditallow); + if (audited) + { + sepgsqlAuditLog(!!denied, + current_page->scontext, + securityRawSecLabelOut(tsid.relid, tsid.secid), + cache->tclass, audited, audit_name); + } + + if (denied) + { + if (!sepgsqlGetEnforce() || cache->permissive) + cache->allowed |= required; /* prevent flood of audit log */ + else + { + if (abort) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: security policy violation"))); + result = false; + } + } + + return result; +} + +/* + * sepgsqlClientCreateSecid + * sepgsqlClientCreateLabel + */ +sepgsql_sid_t +sepgsqlClientCreateSecid(sepgsql_sid_t tsid, uint16 tclass, Oid nrelid) +{ + avc_datum *cache; + + do { + cache = sepgsqlAvcLookup(current_page, tsid, tclass, nrelid); + if (!cache) + cache = sepgsqlAvcMakeEntry(current_page, tsid, tclass, nrelid); + } while (!sepgsqlAvcCheckValid()); + + return cache->nsid; +} + +security_context_t +sepgsqlClientCreateLabel(sepgsql_sid_t tsid, uint16 tclass) +{ + avc_datum *cache; + + do { + cache = sepgsqlAvcLookup(current_page, tsid, tclass, InvalidOid); + if (!cache) + cache = sepgsqlAvcMakeEntry(current_page, tsid, tclass, InvalidOid); + } while (!sepgsqlAvcCheckValid()); + + return cache->ncontext; +} + +/* + * SELinux state monitoring process + * + * This process is forked from postmaster to monitor the state of SELinux. + * SELinux can make a notifier message to userspace object manager via + * netlink socket. When it receives the message, it updates selinux_state + * structure assigned on shared memory region to make any instance reset + * its AVC soon. + */ +static int +sepgsql_cb_log(int type, const char *fmt, ...) +{ + char *c, buffer[1024]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + c = strrchr(buffer, '\n'); + if (c) + *c = '\0'; + + ereport(LOG,(errmsg("%s", buffer))); + + return 0; +} + +static int +sepgsql_cb_setenforce(int enforce) +{ + /* switch enforcing/permissive */ + LWLockAcquire(SepgsqlAvcLock, LW_EXCLUSIVE); + selinux_state->enforcing = (enforce ? true : false); + selinux_state->version++; + LWLockRelease(SepgsqlAvcLock); + + return 0; +} + +static int +sepgsql_cb_policyload(int seqno) +{ + /* invalidate local avc */ + LWLockAcquire(SepgsqlAvcLock, LW_EXCLUSIVE); + selinux_state->version++; + LWLockRelease(SepgsqlAvcLock); + + return 0; +} + +bool +sepgsqlReceiverStart(void) +{ + return sepgsqlIsEnabled(); +} + +void +sepgsqlReceiverMain(void) +{ + union selinux_callback cb; + + Assert(sepgsqlIsEnabled()); + +#ifdef HAVE_SETSID + if (setsid() < 0) + elog(FATAL, "setsid() failed: %m"); +#endif + + /* + * setup the signal handler + */ + pqinitmask(); + pqsignal(SIGHUP, SIG_IGN); + pqsignal(SIGINT, SIG_IGN); + pqsignal(SIGTERM, exit); + pqsignal(SIGQUIT, exit); + pqsignal(SIGUSR1, SIG_IGN); + pqsignal(SIGUSR2, SIG_IGN); + pqsignal(SIGCHLD, SIG_DFL); + PG_SETMASK(&UnBlockSig); + + /* + * map shared memory segment + */ + sepgsqlShmemInit(); + + ereport(LOG, (errmsg("SELinux: netlink receiver (pid=%u)", getpid()))); + + /* + * setup callback functions from avc_netlink_loop() + */ + cb.func_log = sepgsql_cb_log; + selinux_set_callback(SELINUX_CB_LOG, cb); + cb.func_setenforce = sepgsql_cb_setenforce; + selinux_set_callback(SELINUX_CB_SETENFORCE, cb); + cb.func_policyload = sepgsql_cb_policyload; + selinux_set_callback(SELINUX_CB_POLICYLOAD, cb); + + /* + * open netlink socket and wait for messages + */ + avc_netlink_open(1); + + avc_netlink_loop(); + + exit(0); +} + +/* + * sepgsqlInitialize + * + * It sets up the privilege (security context) of the client and initializes + * a few internal stuff. + */ +void +sepgsqlInitialize(void) +{ + if (!sepgsqlIsEnabled()) + return; + + /* + * SE-PgSQL does not prevent anything in single-user mode. + */ + if (!MyProcPort) + sepostgresql_mode = SEPGSQL_MODE_INTERNAL; + + sepgsqlShmemInit(); + + AvcMemCtx = AllocSetContextCreate(TopMemoryContext, + "SE-PgSQL userspace AVC", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + RegisterXactCallback(sepgsqlAvcResetOnAbort, NULL); + RegisterSubXactCallback(sepgsqlAvcResetOnSubAbort, NULL); + + /* + * Set client's security context + */ + sepgsqlSetClientLabel(sepgsqlGetClientLabel()); +} diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index fd248fc..b24239d 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -1329,6 +1329,13 @@ FileTruncate(File file, off_t offset) return returnCode; } +int +FileRawDescriptor(File file) +{ + Assert(FileIsValid(file)); + + return VfdCache[file].fd; +} /* * Routines that want to use stdio (ie, FILE*) should use AllocateFile diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 3022867..cf6fc3d 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -25,6 +25,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgwriter.h" #include "postmaster/postmaster.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" @@ -119,6 +120,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port) #ifdef EXEC_BACKEND size = add_size(size, ShmemBackendArraySize()); #endif + size = add_size(size, sepgsqlShmemSize()); /* freeze the addin request size and include it */ addin_request_allowed = false; diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c index a946972..7abf8e3 100644 --- a/src/backend/storage/large_object/inv_api.c +++ b/src/backend/storage/large_object/inv_api.c @@ -197,14 +197,14 @@ getbytealen(bytea *data) * in use. */ Oid -inv_create(Oid lobjId) +inv_create(Oid lobjId, Oid secid) { Oid lobjId_new; /* * Create a new largeobject with empty data pages */ - lobjId_new = LargeObjectCreate(lobjId); + lobjId_new = LargeObjectCreate(lobjId, secid); /* * dependency on the owner of largeobject diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c index ab2249a..bcc5448 100644 --- a/src/backend/tcop/fastpath.c +++ b/src/backend/tcop/fastpath.c @@ -26,6 +26,7 @@ #include "libpq/pqformat.h" #include "mb/pg_wchar.h" #include "miscadmin.h" +#include "security/sepgsql.h" #include "tcop/fastpath.h" #include "tcop/tcopprot.h" #include "utils/acl.h" @@ -343,11 +344,13 @@ HandleFunctionRequest(StringInfo msgBuf) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(fip->namespace)); + sepgsql_schema_search(fip->namespace, true); aclresult = pg_proc_aclcheck(fid, GetUserId(), ACL_EXECUTE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(fid)); + sepgsql_proc_execute(fid); /* * Prepare function call info block and insert arguments. diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 61b329d..5a379aa 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -573,7 +573,7 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot) Assert(pstmt->returningLists); portal->tupDesc = ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), - false); + false, false); } /* diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 9e82a48..96d25c6 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -50,6 +50,7 @@ #include "postmaster/bgwriter.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteRemove.h" +#include "security/sepgsql.h" #include "storage/fd.h" #include "tcop/pquery.h" #include "tcop/utility.h" @@ -162,6 +163,7 @@ check_xact_readonly(Node *parsetree) case T_AlterRoleSetStmt: case T_AlterObjectSchemaStmt: case T_AlterOwnerStmt: + case T_AlterSecLabelStmt: case T_AlterSeqStmt: case T_AlterTableStmt: case T_RenameStmt: @@ -634,6 +636,10 @@ ProcessUtility(Node *parsetree, ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); break; + case T_AlterSecLabelStmt: + ExecAlterSecLabelStmt((AlterSecLabelStmt *) parsetree); + break; + case T_AlterTableStmt: { List *stmts; @@ -917,6 +923,7 @@ ProcessUtility(Node *parsetree, LoadStmt *stmt = (LoadStmt *) parsetree; closeAllVfds(); /* probably not necessary... */ + /* Allowed names are restricted if you're not superuser */ load_file(stmt->filename, !superuser()); } @@ -1664,6 +1671,31 @@ CreateCommandTag(Node *parsetree) } break; + case T_AlterSecLabelStmt: + switch (((AlterSecLabelStmt *) parsetree)->objectType) + { + case OBJECT_DATABASE: + tag = "ALTER DATABASE"; + break; + case OBJECT_SCHEMA: + tag = "ALTER SCHEMA"; + break; + case OBJECT_TABLE: + case OBJECT_COLUMN: + tag = "ALTER TABLE"; + break; + case OBJECT_SEQUENCE: + tag = "ALTER SEQUENCE"; + break; + case OBJECT_FUNCTION: + tag = "ALTER FUNCTION"; + break; + default: + tag = "???"; + break; + } + break; + case T_AlterTableStmt: switch (((AlterTableStmt *) parsetree)->relkind) { @@ -2242,6 +2274,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_AlterSecLabelStmt: + lev = LOGSTMT_DDL; + break; + case T_AlterTableStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index e1328dc..7041047 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -24,6 +24,7 @@ #include "funcapi.h" #include "miscadmin.h" #include "postmaster/syslogger.h" +#include "security/sepgsql.h" #include "storage/fd.h" #include "utils/builtins.h" #include "utils/memutils.h" @@ -99,6 +100,9 @@ pg_read_file(PG_FUNCTION_ARGS) filename = convert_and_check_filename(filename_t); + /* SELinux: check file:{read} permission */ + sepgsql_file_read(filename); + if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) ereport(ERROR, (errcode_for_file_access(), @@ -159,6 +163,8 @@ pg_stat_file(PG_FUNCTION_ARGS) (errmsg("must be superuser to get file information")))); filename = convert_and_check_filename(filename_t); + /* SELinux: check file:{getattr} permission */ + sepgsql_file_stat(filename); if (stat(filename, &fst) < 0) ereport(ERROR, diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 81684e3..b681214 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -39,6 +39,7 @@ #include "parser/parse_coerce.h" #include "parser/parse_relation.h" #include "miscadmin.h" +#include "security/rowlevel.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -2627,6 +2628,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) const char *sep; int i; int old_work_mem; + int save_rowlv; char workmembuf[32]; int spi_result; SPIPlanPtr qplan; @@ -2759,6 +2761,11 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) SPI_result, querybuf.data); /* + * Disables the Row-level stuff during the internal consistency checks. + */ + save_rowlv = rowlvSetPerformingMode(ROWLV_BYPASS_MODE); + + /* * Run the plan. For safety we force a current snapshot to be used. (In * serializable mode, this arguably violates serializability, but we * really haven't got much choice.) We don't need to register the @@ -2771,6 +2778,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) InvalidSnapshot, true, false, 1); + /* Restore Row-level stuff */ + rowlvSetPerformingMode(save_rowlv); + /* Check result */ if (spi_result != SPI_OK_SELECT) elog(ERROR, "SPI_execute_snapshot returned %d", spi_result); @@ -3265,6 +3275,7 @@ ri_PerformCheck(RI_QueryKey *qkey, SPIPlanPtr qplan, int spi_result; Oid save_userid; int save_sec_context; + int save_rowlv, temp_rowlv; Datum vals[RI_MAX_NUMKEYS * 2]; char nulls[RI_MAX_NUMKEYS * 2]; @@ -3348,12 +3359,19 @@ ri_PerformCheck(RI_QueryKey *qkey, SPIPlanPtr qplan, SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); + /* Switch Row-level stuff behavior on FK checks, if necessary */ + temp_rowlv = (detectNewRows ? ROWLV_ABORT_MODE : ROWLV_FILTER_MODE); + save_rowlv = rowlvSetPerformingMode(temp_rowlv); + /* Finally we can run the query. */ spi_result = SPI_execute_snapshot(qplan, vals, nulls, test_snapshot, crosscheck_snapshot, false, false, limit); + /* Restore Row-level stuff behavior */ + rowlvSetPerformingMode(save_rowlv); + /* Restore UID and security context */ SetUserIdAndSecContext(save_userid, save_sec_context); diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c index 5bb166d..ef9c2e9 100644 --- a/src/backend/utils/adt/tid.c +++ b/src/backend/utils/adt/tid.c @@ -27,6 +27,7 @@ #include "libpq/pqformat.h" #include "miscadmin.h" #include "parser/parsetree.h" +#include "security/sepgsql.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/rel.h" @@ -347,6 +348,8 @@ currtid_byreloid(PG_FUNCTION_ARGS) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(rel)); + /* SELinux checks */ + sepgsql_relation_get_transaction_id(RelationGetRelid(rel)); if (rel->rd_rel->relkind == RELKIND_VIEW) return currtid_for_view(rel, tid); @@ -377,6 +380,8 @@ currtid_byrelname(PG_FUNCTION_ARGS) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(rel)); + /* SELinux checks */ + sepgsql_relation_get_transaction_id(RelationGetRelid(rel)); if (rel->rd_rel->relkind == RELKIND_VIEW) return currtid_for_view(rel, tid); diff --git a/src/backend/utils/adt/trigfuncs.c b/src/backend/utils/adt/trigfuncs.c index ceab88d..80db58d 100644 --- a/src/backend/utils/adt/trigfuncs.c +++ b/src/backend/utils/adt/trigfuncs.c @@ -76,6 +76,10 @@ suppress_redundant_updates_trigger(PG_FUNCTION_ARGS) !OidIsValid(HeapTupleHeaderGetOid(newheader))) HeapTupleHeaderSetOid(newheader, HeapTupleHeaderGetOid(oldheader)); + if (HeapTupleHeaderHasSecid(newheader) && + !OidIsValid(HeapTupleHeaderGetSecid(newheader))) + HeapTupleHeaderSetSecid(newheader, HeapTupleHeaderGetSecid(oldheader)); + /* if the tuple payload is the same ... */ if (newtuple->t_len == oldtuple->t_len && newheader->t_hoff == oldheader->t_hoff && diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 8fc2d5a..b943e80 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -859,12 +859,12 @@ PlanCacheComputeResultDesc(List *stmt_list) if (IsA(node, Query)) { query = (Query *) node; - return ExecCleanTypeFromTL(query->targetList, false); + return ExecCleanTypeFromTL(query->targetList, false, false); } if (IsA(node, PlannedStmt)) { pstmt = (PlannedStmt *) node; - return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false); + return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false, false); } /* other cases shouldn't happen, but return NULL */ break; @@ -875,13 +875,14 @@ PlanCacheComputeResultDesc(List *stmt_list) { query = (Query *) node; Assert(query->returningList); - return ExecCleanTypeFromTL(query->returningList, false); + return ExecCleanTypeFromTL(query->returningList, false, false); } if (IsA(node, PlannedStmt)) { pstmt = (PlannedStmt *) node; Assert(pstmt->returningLists); - return ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), false); + return ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), + false, false); } /* other cases shouldn't happen, but return NULL */ break; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index df0a172..a48d474 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -55,6 +55,7 @@ #include "optimizer/prep.h" #include "optimizer/var.h" #include "rewrite/rewriteDefine.h" +#include "security/sepgsql.h" #include "storage/fd.h" #include "storage/lmgr.h" #include "storage/smgr.h" @@ -865,6 +866,10 @@ RelationBuildDesc(Oid targetRelId, Relation oldrelation) /* extract reloptions if any */ RelationParseRelOptions(relation, pg_class_tuple); + /* Fixup relation->rd_att->tdhassecid */ + RelationGetDescr(relation)->tdhassecid + = securityTupleDescHasSecid(relid, relp->relkind); + /* * initialize the relation lock manager information */ @@ -1460,6 +1465,11 @@ formrdesc(const char *relationName, Oid relationReltype, RelationGetRelid(relation) = relation->rd_att->attrs[0]->attrelid; relation->rd_rel->relfilenode = RelationGetRelid(relation); + /* Fixup relation->rd_att->tdhassecid */ + RelationGetDescr(relation)->tdhassecid + = securityTupleDescHasSecid(RelationGetRelid(relation), + RELKIND_RELATION); + /* * initialize the relation lock manager information */ @@ -2749,6 +2759,13 @@ BuildHardcodedDescriptor(int natts, Form_pg_attribute attrs, bool hasoids) result = CreateTemplateTupleDesc(natts, hasoids); result->tdtypeid = RECORDOID; /* not right, but we don't care */ result->tdtypmod = -1; + /* + * NOTE: we assume the returned TupleDesc is only used for + * references to toast'ed data, and it is not delivered to + * heap_form_tuple(), so TupleDesc->tdhassecid don't give us + * any effect. + * We omit to invoke securityTupleDescHasSecid() here. + */ for (i = 0; i < natts; i++) { @@ -3503,6 +3520,11 @@ load_relcache_init_file(void) rel->rd_options = NULL; } + /* Fixup rel->rd_att->tdhassecid */ + RelationGetDescr(rel)->tdhassecid + = securityTupleDescHasSecid(RelationGetRelid(rel), + RelationGetForm(rel)->relkind); + /* mark not-null status */ if (has_not_null) { diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c index 529940c..fe06e2f 100644 --- a/src/backend/utils/fmgr/dfmgr.c +++ b/src/backend/utils/fmgr/dfmgr.c @@ -23,6 +23,7 @@ #endif #include "lib/stringinfo.h" #include "miscadmin.h" +#include "security/sepgsql.h" #include "utils/dynamic_loader.h" #include "utils/hsearch.h" @@ -109,6 +110,9 @@ load_external_function(char *filename, char *funcname, /* Expand the possibly-abbreviated filename to an exact path name */ fullname = expand_dynamic_library_name(filename); + /* SELinux checks db_database:{load_module} */ + sepgsql_database_load_module(MyDatabaseId, fullname); + /* Load the shared library, unless we already did */ lib_handle = internal_load_library(fullname); @@ -149,6 +153,9 @@ load_file(const char *filename, bool restricted) /* Expand the possibly-abbreviated filename to an exact path name */ fullname = expand_dynamic_library_name(filename); + /* SELinux checks db_database:{load_module} */ + sepgsql_database_load_module(MyDatabaseId, fullname); + /* Unload the library if currently loaded */ internal_unload_library(fullname); diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 9a1da59..76264f3 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -24,6 +24,7 @@ #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "pgstat.h" +#include "security/sepgsql.h" #include "utils/builtins.h" #include "utils/fmgrtab.h" #include "utils/guc.h" @@ -232,6 +233,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, */ if (!ignore_security && (procedureStruct->prosecdef || + sepgsql_proc_entrypoint(procedureTuple) || !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig))) { finfo->fn_addr = fmgr_security_definer; @@ -860,6 +862,7 @@ struct fmgr_security_definer_cache { FmgrInfo flinfo; /* lookup info for target function */ Oid userid; /* userid to set, or InvalidOid */ + char *seclabel; /* security label to set, or NULL */ ArrayType *proconfig; /* GUC values to set, or NULL */ }; @@ -881,6 +884,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS) FmgrInfo *save_flinfo; Oid save_userid; int save_sec_context; + char *save_label = NULL; volatile int save_nestlevel; PgStat_FunctionCallUsage fcusage; @@ -910,6 +914,9 @@ fmgr_security_definer(PG_FUNCTION_ARGS) if (procedureStruct->prosecdef) fcache->userid = procedureStruct->proowner; + fcache->seclabel + = sepgsql_proc_trusted(tuple, fcinfo->flinfo->fn_mcxt); + datum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proconfig, &isnull); if (!isnull) @@ -936,6 +943,8 @@ fmgr_security_definer(PG_FUNCTION_ARGS) if (OidIsValid(fcache->userid)) SetUserIdAndSecContext(fcache->userid, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); + if (fcache->seclabel) + save_label = sepgsqlSetClientLabel(fcache->seclabel); if (fcache->proconfig) { @@ -983,6 +992,8 @@ fmgr_security_definer(PG_FUNCTION_ARGS) AtEOXact_GUC(true, save_nestlevel); if (OidIsValid(fcache->userid)) SetUserIdAndSecContext(save_userid, save_sec_context); + if (fcache->seclabel) + sepgsqlSetClientLabel(save_label); return result; } diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 327ba7c..0fc1c8d 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -32,6 +32,7 @@ #include "pgstat.h" #include "postmaster/autovacuum.h" #include "postmaster/postmaster.h" +#include "security/sepgsql.h" #include "storage/backendid.h" #include "storage/bufmgr.h" #include "storage/fd.h" @@ -201,7 +202,7 @@ CheckMyDatabase(const char *name, bool am_superuser) name))); /* - * Check privilege to connect to the database. (The am_superuser test + * Check privilege to connect to the database. (The am_superuser test * is redundant, but since we have the flag, might as well check it * and save a few cycles.) */ @@ -213,6 +214,9 @@ CheckMyDatabase(const char *name, bool am_superuser) errmsg("permission denied for database \"%s\"", name), errdetail("User does not have CONNECT privilege."))); + /* SELinux: db_database:{access} */ + sepgsql_database_access(MyDatabaseId); + /* * Check connection limit for this database. * @@ -607,6 +611,9 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username, /* set up ACL framework (so CheckMyDatabase can check permissions) */ initialize_acl(); + /* Initialize SE-PostgreSQL */ + sepgsqlInitialize(); + /* * Read the real pg_database row for our database, check permissions and * set up database-specific GUC settings. We can't do this until all the diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index db1d933..568ef80 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -57,6 +57,7 @@ #include "postmaster/syslogger.h" #include "postmaster/walwriter.h" #include "regex/regex.h" +#include "security/sepgsql.h" #include "storage/bufmgr.h" #include "storage/fd.h" #include "tcop/tcopprot.h" @@ -257,6 +258,18 @@ static const struct config_enum_entry isolation_level_options[] = { {NULL, 0} }; +#ifdef HAVE_SELINUX +static const struct config_enum_entry sepostgresql_mode_options [] = { + {"on", SEPGSQL_MODE_DEFAULT, true}, + {"off", SEPGSQL_MODE_DISABLED, true}, + {"default", SEPGSQL_MODE_DEFAULT, false}, + {"permissive", SEPGSQL_MODE_PERMISSIVE, false}, + {"enforcing", SEPGSQL_MODE_ENFORCING, false}, + {"disabled", SEPGSQL_MODE_DISABLED, false}, + {NULL, 0} +}; +#endif + static const struct config_enum_entry session_replication_role_options[] = { {"origin", SESSION_REPLICATION_ROLE_ORIGIN, false}, {"replica", SESSION_REPLICATION_ROLE_REPLICA, false}, @@ -1221,6 +1234,24 @@ static struct config_bool ConfigureNamesBool[] = &IgnoreSystemIndexes, false, NULL, NULL }, +#ifdef HAVE_SELINUX + { + {"sepostgresql_row_level", PGC_POSTMASTER, CONN_AUTH_SECURITY, + gettext_noop("Row-level access controls on SE-PostgreSQL"), + NULL, + }, + &sepostgresql_row_level, + true, NULL, NULL + }, + { + {"sepostgresql_mcstrans", PGC_USERSET, CONN_AUTH_SECURITY, + gettext_noop("SE-PostgreSQL uses mcstrans on printing security labels"), + NULL, + }, + &sepostgresql_mcstrans, + true, NULL, NULL + }, +#endif { {"lo_compat_privileges", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS, @@ -2640,7 +2671,17 @@ static struct config_enum ConfigureNamesEnum[] = ®ex_flavor, REG_ADVANCED, regex_flavor_options, NULL, NULL }, - +#ifdef HAVE_SELINUX + { + {"sepostgresql", PGC_POSTMASTER, CONN_AUTH_SECURITY, + gettext_noop("SE-PostgreSQL performing mode"), + NULL, + }, + &sepostgresql_mode, + SEPGSQL_MODE_DISABLED, sepostgresql_mode_options, + NULL, sepgsqlShowMode + }, +#endif { {"session_replication_role", PGC_SUSET, CLIENT_CONN_STATEMENT, gettext_noop("Sets the session's behavior for triggers and rewrite rules."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 85acc4e..e298197 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -51,7 +51,7 @@ #------------------------------------------------------------------------------ -# CONNECTIONS AND AUTHENTICATION +# CONNECTIONS, AUTHENTICATION AND SECURITY #------------------------------------------------------------------------------ # - Connection Settings - @@ -95,7 +95,7 @@ # 0 selects the system default #tcp_keepalives_count = 0 # TCP_KEEPCNT; # 0 selects the system default - +#sepostgresql = off # SE-PostgreSQL support #------------------------------------------------------------------------------ # RESOURCE USAGE (except WAL) diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 1fcf590..620ac5c 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -87,6 +87,7 @@ static bool debug = false; static bool noclean = false; static bool show_setting = false; static char *xlog_dir = ""; +static bool enable_selinux = false; /* internal vars */ @@ -1205,6 +1206,13 @@ setup_config(void) "#default_text_search_config = 'pg_catalog.simple'", repltok); + if (enable_selinux) + { + strcpy(repltok, "sepostgresql = on"); + conflines = replace_token(conflines, + "#sepostgresql = off", repltok); + } + snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data); writefile(path, conflines); @@ -2444,6 +2452,7 @@ usage(const char *progname) printf(_(" -U, --username=NAME database superuser name\n")); printf(_(" -W, --pwprompt prompt for a password for the new superuser\n")); printf(_(" -X, --xlogdir=XLOGDIR location for the transaction log directory\n")); + printf(_(" --enable-selinux enables SELinux support, if compiled\n")); printf(_("\nLess commonly used options:\n")); printf(_(" -d, --debug generate lots of debugging output\n")); printf(_(" -L DIRECTORY where to find the input files\n")); @@ -2479,6 +2488,7 @@ main(int argc, char *argv[]) {"auth", required_argument, NULL, 'A'}, {"pwprompt", no_argument, NULL, 'W'}, {"pwfile", required_argument, NULL, 9}, + {"enable-selinux", no_argument, NULL, 10}, {"username", required_argument, NULL, 'U'}, {"help", no_argument, NULL, '?'}, {"version", no_argument, NULL, 'V'}, @@ -2595,6 +2605,9 @@ main(int argc, char *argv[]) case 9: pwfilename = xstrdup(optarg); break; + case 10: + enable_selinux = true; + break; case 's': show_setting = true; break; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 57b5f7d..4e48601 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -112,6 +112,8 @@ static int disable_dollar_quoting = 0; static int dump_inserts = 0; static int column_inserts = 0; +/* flag to turn on/off security_context */ +static int security_context = 0; static void help(const char *progname); static void expand_schema_name_patterns(SimpleStringList *patterns, @@ -277,6 +279,7 @@ main(int argc, char **argv) {"no-tablespaces", no_argument, &outputNoTablespaces, 1}, {"role", required_argument, NULL, 3}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, + {"security-context", no_argument, &security_context, 1}, {NULL, 0, NULL, 0} }; @@ -425,6 +428,8 @@ main(int argc, char **argv) outputNoTablespaces = 1; else if (strcmp(optarg, "use-set-session-authorization") == 0) use_setsessauth = 1; + else if (strcmp(optarg, "security-context") == 0) + security_context = 1; else { fprintf(stderr, @@ -573,6 +578,28 @@ main(int argc, char **argv) std_strings = PQparameterStatus(g_conn, "standard_conforming_strings"); g_fout->std_strings = (std_strings && strcmp(std_strings, "on") == 0); + /* Check availability of SE-PostgreSQL */ + if (security_context > 0) + { + PGresult *res; + + res = PQexec(g_conn, "SHOW sepostgresql"); + if (PQresultStatus(res) != PGRES_TUPLES_OK || + PQntuples(res) != 1 || + strcmp(PQgetvalue(res, 0, 0), "on") != 0) + { + write_msg(NULL, "SE-PostgreSQL is not available now."); + exit(1); + } + } + + /* + * It needs to force column insertion mode, when --inserts + * and either --security-label or --security-acl is given. + */ + if (security_context > 0 && dump_inserts) + column_inserts = 1; + /* Set the role if requested */ if (use_role && g_fout->remoteVersion >= 80100) { @@ -826,6 +853,8 @@ help(const char *progname) printf(_(" --use-set-session-authorization\n" " use SET SESSION AUTHORIZATION commands instead of\n" " ALTER OWNER commands to set ownership\n")); + printf(_(" --security-label dump SE-PostgreSQL security labels\n")); + printf(_(" --security-acl dump row-level database ACLs\n")); printf(_("\nConnection options:\n")); printf(_(" -h, --host=HOSTNAME database server host or socket directory\n")); @@ -1227,7 +1256,8 @@ dumpTableData_insert(Archive *fout, void *dcontext) if (fout->remoteVersion >= 70100) { appendPQExpBuffer(q, "DECLARE _pg_dump_cursor CURSOR FOR " - "SELECT * FROM ONLY %s", + "SELECT %s* FROM ONLY %s", + (security_context > 0 ? "security_context, " : ""), fmtQualifiedId(tbinfo->dobj.namespace->dobj.name, classname)); } @@ -1583,7 +1613,8 @@ dumpDatabase(Archive *AH) i_collate, i_ctype, i_frozenxid, - i_tablespace; + i_tablespace, + i_seclabel; CatalogId dbCatId; DumpId dbDumpId; const char *datname, @@ -1591,7 +1622,8 @@ dumpDatabase(Archive *AH) *encoding, *collate, *ctype, - *tablespace; + *tablespace, + *seclabel; uint32 frozenxid; datname = PQdb(g_conn); @@ -1610,11 +1642,12 @@ dumpDatabase(Archive *AH) "pg_encoding_to_char(encoding) AS encoding, " "datcollate, datctype, datfrozenxid, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " - "shobj_description(oid, 'pg_database') AS description " - + "shobj_description(oid, 'pg_database') AS description, " + "%s as security_context " "FROM pg_database " "WHERE datname = ", - username_subquery); + username_subquery, + security_context ? "security_context" : "NULL"); appendStringLiteralAH(dbQry, datname, AH); } else if (g_fout->remoteVersion >= 80200) @@ -1624,8 +1657,8 @@ dumpDatabase(Archive *AH) "pg_encoding_to_char(encoding) AS encoding, " "NULL AS datcollate, NULL AS datctype, datfrozenxid, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " - "shobj_description(oid, 'pg_database') AS description " - + "shobj_description(oid, 'pg_database') AS description, " + "NULL as security_context " "FROM pg_database " "WHERE datname = ", username_subquery); @@ -1637,7 +1670,8 @@ dumpDatabase(Archive *AH) "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "NULL AS datcollate, NULL AS datctype, datfrozenxid, " - "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace " + "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " + "NULL as security_context " "FROM pg_database " "WHERE datname = ", username_subquery); @@ -1650,7 +1684,8 @@ dumpDatabase(Archive *AH) "pg_encoding_to_char(encoding) AS encoding, " "NULL AS datcollate, NULL AS datctype, " "0 AS datfrozenxid, " - "NULL AS tablespace " + "NULL AS tablespace, " + "NULL AS security_context " "FROM pg_database " "WHERE datname = ", username_subquery); @@ -1665,7 +1700,8 @@ dumpDatabase(Archive *AH) "pg_encoding_to_char(encoding) AS encoding, " "NULL AS datcollate, NULL AS datctype, " "0 AS datfrozenxid, " - "NULL AS tablespace " + "NULL AS tablespace, " + "NULL as security_context " "FROM pg_database " "WHERE datname = ", username_subquery); @@ -1699,6 +1735,7 @@ dumpDatabase(Archive *AH) i_ctype = PQfnumber(res, "datctype"); i_frozenxid = PQfnumber(res, "datfrozenxid"); i_tablespace = PQfnumber(res, "tablespace"); + i_seclabel = PQfnumber(res, "security_context"); dbCatId.tableoid = atooid(PQgetvalue(res, 0, i_tableoid)); dbCatId.oid = atooid(PQgetvalue(res, 0, i_oid)); @@ -1708,6 +1745,7 @@ dumpDatabase(Archive *AH) ctype = PQgetvalue(res, 0, i_ctype); frozenxid = atooid(PQgetvalue(res, 0, i_frozenxid)); tablespace = PQgetvalue(res, 0, i_tablespace); + seclabel = PQgetvalue(res, 0, i_seclabel); appendPQExpBuffer(creaQry, "CREATE DATABASE %s WITH TEMPLATE = template0", fmtId(datname)); @@ -1729,6 +1767,9 @@ dumpDatabase(Archive *AH) if (strlen(tablespace) > 0 && strcmp(tablespace, "pg_default") != 0) appendPQExpBuffer(creaQry, " TABLESPACE = %s", fmtId(tablespace)); + if (strlen(seclabel) > 0) + appendPQExpBuffer(creaQry, " SECURITY_CONTEXT = '%s'", seclabel); + appendPQExpBuffer(creaQry, ";\n"); if (binary_upgrade) @@ -3230,6 +3271,7 @@ getTables(int *numTables) int i_reltablespace; int i_reloptions; int i_toastreloptions; + int i_relseclabel; /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); @@ -3271,7 +3313,8 @@ getTables(int *numTables) "d.refobjsubid AS owning_col, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " - "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " + "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, " + "%s as security_context " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -3282,6 +3325,7 @@ getTables(int *numTables) "WHERE c.relkind in ('%c', '%c', '%c', '%c') " "ORDER BY c.oid", username_subquery, + security_context ? "c.security_context" : "NULL", RELKIND_SEQUENCE, RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW, RELKIND_COMPOSITE_TYPE); @@ -3303,7 +3347,8 @@ getTables(int *numTables) "d.refobjsubid AS owning_col, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL as security_context " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -3334,7 +3379,8 @@ getTables(int *numTables) "d.refobjsubid AS owning_col, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL as security_context " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -3365,7 +3411,8 @@ getTables(int *numTables) "d.refobjsubid AS owning_col, " "NULL AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL as security_context " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -3392,7 +3439,8 @@ getTables(int *numTables) "NULL::int4 AS owning_col, " "NULL AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL AS security_context " "FROM pg_class " "WHERE relkind IN ('%c', '%c', '%c') " "ORDER BY oid", @@ -3414,7 +3462,8 @@ getTables(int *numTables) "NULL::int4 AS owning_col, " "NULL AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL AS security_context " "FROM pg_class " "WHERE relkind IN ('%c', '%c', '%c') " "ORDER BY oid", @@ -3446,7 +3495,8 @@ getTables(int *numTables) "NULL::int4 AS owning_col, " "NULL AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL as security_context " "FROM pg_class c " "WHERE relkind IN ('%c', '%c') " "ORDER BY oid", @@ -3491,6 +3541,7 @@ getTables(int *numTables) i_reltablespace = PQfnumber(res, "reltablespace"); i_reloptions = PQfnumber(res, "reloptions"); i_toastreloptions = PQfnumber(res, "toast_reloptions"); + i_relseclabel = PQfnumber(res, "security_context"); if (lockWaitTimeout && g_fout->remoteVersion >= 70300) { @@ -3538,6 +3589,7 @@ getTables(int *numTables) tblinfo[i].reltablespace = strdup(PQgetvalue(res, i, i_reltablespace)); tblinfo[i].reloptions = strdup(PQgetvalue(res, i, i_reloptions)); tblinfo[i].toast_reloptions = strdup(PQgetvalue(res, i, i_toastreloptions)); + tblinfo[i].relseclabel = strdup(PQgetvalue(res, i, i_relseclabel)); /* other fields were zeroed above */ @@ -4737,6 +4789,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables) int i_attlen; int i_attalign; int i_attislocal; + int i_attseclabel; PGresult *res; int ntups; bool hasdefaults; @@ -4781,12 +4834,14 @@ getTableAttrs(TableInfo *tblinfo, int numTables) "a.attstattarget, a.attstorage, t.typstorage, " "a.attnotnull, a.atthasdef, a.attisdropped, " "a.attlen, a.attalign, a.attislocal, " - "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname " + "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, " + "%s as security_context " "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " "ON a.atttypid = t.oid " "WHERE a.attrelid = '%u'::pg_catalog.oid " "AND a.attnum > 0::pg_catalog.int2 " "ORDER BY a.attrelid, a.attnum", + security_context ? "a.security_context" : "NULL", tbinfo->dobj.catId.oid); } else if (g_fout->remoteVersion >= 70100) @@ -4801,7 +4856,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables) "t.typstorage, a.attnotnull, a.atthasdef, " "false AS attisdropped, a.attlen, " "a.attalign, false AS attislocal, " - "format_type(t.oid,a.atttypmod) AS atttypname " + "format_type(t.oid,a.atttypmod) AS atttypname, " + "NULL as security_context " "FROM pg_attribute a LEFT JOIN pg_type t " "ON a.atttypid = t.oid " "WHERE a.attrelid = '%u'::oid " @@ -4818,7 +4874,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables) "attnotnull, atthasdef, false AS attisdropped, " "attlen, attalign, " "false AS attislocal, " - "(SELECT typname FROM pg_type WHERE oid = atttypid) AS atttypname " + "(SELECT typname FROM pg_type WHERE oid = atttypid) AS atttypname, " + "NULL as security_context " "FROM pg_attribute a " "WHERE attrelid = '%u'::oid " "AND attnum > 0::int2 " @@ -4844,6 +4901,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables) i_attlen = PQfnumber(res, "attlen"); i_attalign = PQfnumber(res, "attalign"); i_attislocal = PQfnumber(res, "attislocal"); + i_attseclabel = PQfnumber(res, "security_context"); tbinfo->numatts = ntups; tbinfo->attnames = (char **) malloc(ntups * sizeof(char *)); @@ -4856,6 +4914,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables) tbinfo->attlen = (int *) malloc(ntups * sizeof(int)); tbinfo->attalign = (char *) malloc(ntups * sizeof(char)); tbinfo->attislocal = (bool *) malloc(ntups * sizeof(bool)); + tbinfo->attseclabel = (char **) malloc(ntups * sizeof(char *)); tbinfo->notnull = (bool *) malloc(ntups * sizeof(bool)); tbinfo->attrdefs = (AttrDefInfo **) malloc(ntups * sizeof(AttrDefInfo *)); tbinfo->inhAttrs = (bool *) malloc(ntups * sizeof(bool)); @@ -4881,6 +4940,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables) tbinfo->attlen[j] = atoi(PQgetvalue(res, j, i_attlen)); tbinfo->attalign[j] = *(PQgetvalue(res, j, i_attalign)); tbinfo->attislocal[j] = (PQgetvalue(res, j, i_attislocal)[0] == 't'); + tbinfo->attseclabel[j] = strdup(PQgetvalue(res, j, i_attseclabel)); tbinfo->notnull[j] = (PQgetvalue(res, j, i_attnotnull)[0] == 't'); tbinfo->attrdefs[j] = NULL; /* fix below */ if (PQgetvalue(res, j, i_atthasdef)[0] == 't') @@ -7131,6 +7191,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo) char *proconfig; char *procost; char *prorows; + char *proseclabel; char *lanname; char *rettypename; int nallargs; @@ -7167,9 +7228,11 @@ dumpFunc(Archive *fout, FuncInfo *finfo) "pg_catalog.pg_get_function_result(oid) AS funcresult, " "proiswindow, provolatile, proisstrict, prosecdef, " "proconfig, procost, prorows, " - "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " + "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname, " + "%s as security_context " "FROM pg_catalog.pg_proc " "WHERE oid = '%u'::pg_catalog.oid", + security_context ? "security_context" : "NULL", finfo->dobj.catId.oid); } else if (g_fout->remoteVersion >= 80300) @@ -7180,7 +7243,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo) "false AS proiswindow, " "provolatile, proisstrict, prosecdef, " "proconfig, procost, prorows, " - "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " + "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname, " + "NULL AS security_context " "FROM pg_catalog.pg_proc " "WHERE oid = '%u'::pg_catalog.oid", finfo->dobj.catId.oid); @@ -7193,7 +7257,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo) "false AS proiswindow, " "provolatile, proisstrict, prosecdef, " "null AS proconfig, 0 AS procost, 0 AS prorows, " - "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " + "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname, " + "NULL AS security_context " "FROM pg_catalog.pg_proc " "WHERE oid = '%u'::pg_catalog.oid", finfo->dobj.catId.oid); @@ -7208,7 +7273,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo) "false AS proiswindow, " "provolatile, proisstrict, prosecdef, " "null AS proconfig, 0 AS procost, 0 AS prorows, " - "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " + "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname, " + "NULL AS security_context " "FROM pg_catalog.pg_proc " "WHERE oid = '%u'::pg_catalog.oid", finfo->dobj.catId.oid); @@ -7223,7 +7289,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo) "false AS proiswindow, " "provolatile, proisstrict, prosecdef, " "null AS proconfig, 0 AS procost, 0 AS prorows, " - "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " + "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname, " + "NULL AS security_context " "FROM pg_catalog.pg_proc " "WHERE oid = '%u'::pg_catalog.oid", finfo->dobj.catId.oid); @@ -7240,7 +7307,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo) "proisstrict, " "false AS prosecdef, " "null AS proconfig, 0 AS procost, 0 AS prorows, " - "(SELECT lanname FROM pg_language WHERE oid = prolang) AS lanname " + "(SELECT lanname FROM pg_language WHERE oid = prolang) AS lanname, " + "NULL AS security_context " "FROM pg_proc " "WHERE oid = '%u'::oid", finfo->dobj.catId.oid); @@ -7257,7 +7325,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo) "false AS proisstrict, " "false AS prosecdef, " "NULL AS proconfig, 0 AS procost, 0 AS prorows, " - "(SELECT lanname FROM pg_language WHERE oid = prolang) AS lanname " + "(SELECT lanname FROM pg_language WHERE oid = prolang) AS lanname, " + "NULL AS security_context " "FROM pg_proc " "WHERE oid = '%u'::oid", finfo->dobj.catId.oid); @@ -7301,6 +7370,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo) proconfig = PQgetvalue(res, 0, PQfnumber(res, "proconfig")); procost = PQgetvalue(res, 0, PQfnumber(res, "procost")); prorows = PQgetvalue(res, 0, PQfnumber(res, "prorows")); + proseclabel = PQgetvalue(res, 0, PQfnumber(res, "security_context")); lanname = PQgetvalue(res, 0, PQfnumber(res, "lanname")); /* @@ -7459,6 +7529,9 @@ dumpFunc(Archive *fout, FuncInfo *finfo) if (prosecdef[0] == 't') appendPQExpBuffer(q, " SECURITY DEFINER"); + if (security_context > 0 && strlen(proseclabel) > 0) + appendPQExpBuffer(q, " SECURITY_CONTEXT = '%s'", proseclabel); + /* * COST and ROWS are emitted only if present and not default, so as not to * break backwards-compatibility of the dump without need. Keep this code @@ -9917,6 +9990,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) if (tbinfo->notnull[j] && (!tbinfo->inhNotNull[j] || binary_upgrade)) appendPQExpBuffer(q, " NOT NULL"); + + /* + * Security label -- if SE-PostgreSQL enabled + */ + if (security_context > 0 && + strlen(tbinfo->attseclabel[j]) > 0 && + strcmp(tbinfo->relseclabel, tbinfo->attseclabel[j]) != 0) + appendPQExpBuffer(q, " SECURITY_CONTEXT = '%s'", + tbinfo->attseclabel[j]); + + actual_atts++; } } @@ -9979,6 +10063,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) appendPQExpBuffer(q, ")"); } + if (security_context > 0 && strlen(tbinfo->relseclabel) > 0) + appendPQExpBuffer(q, " SECURITY_CONTEXT = '%s'", tbinfo->relseclabel); + appendPQExpBuffer(q, ";\n"); /* @@ -11550,6 +11637,13 @@ fmtCopyColumnList(const TableInfo *ti) appendPQExpBuffer(q, "("); needComma = false; + + if (security_context > 0) + { + appendPQExpBuffer(q, "security_context"); + needComma = true; + } + for (i = 0; i < numatts; i++) { if (attisdropped[i]) diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index a9b3dae..350025c 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -228,6 +228,7 @@ typedef struct _tableInfo bool hasoids; /* does it have OIDs? */ uint32 frozenxid; /* for restore frozen xid */ int ncheck; /* # of CHECK expressions */ + char *relseclabel; /* security labels of relation */ /* these two are set only if table is a sequence owned by a column: */ Oid owning_tab; /* OID of table owning sequence */ int owning_col; /* attr # of column owning sequence */ @@ -249,6 +250,7 @@ typedef struct _tableInfo int *attlen; /* attribute length, used by binary_upgrade */ char *attalign; /* attribute align, used by binary_upgrade */ bool *attislocal; /* true if attr has local definition */ + char **attseclabel; /* security labels of attributes */ /* * Note: we need to store per-attribute notnull, default, and constraint diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index b265398..5011551 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -69,6 +69,9 @@ static int no_tablespaces = 0; static int use_setsessauth = 0; static int server_version; +static int security_label = 0; +static int security_acl = 0; + static FILE *OPF; static char *filename = NULL; @@ -130,6 +133,8 @@ main(int argc, char *argv[]) {"no-tablespaces", no_argument, &no_tablespaces, 1}, {"role", required_argument, NULL, 3}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, + {"security-label", no_argument, &security_label, 1}, + {"security-acl", no_argument, &security_acl, 1}, {NULL, 0, NULL, 0} }; @@ -283,6 +288,10 @@ main(int argc, char *argv[]) no_tablespaces = 1; else if (strcmp(optarg, "use-set-session-authorization") == 0) use_setsessauth = 1; + else if (strcmp(optarg, "security-label") == 0) + security_label = 1; + else if (strcmp(optarg, "security-acl") == 0) + security_acl = 1; else { fprintf(stderr, @@ -328,6 +337,10 @@ main(int argc, char *argv[]) appendPQExpBuffer(pgdumpopts, " --no-tablespaces"); if (use_setsessauth) appendPQExpBuffer(pgdumpopts, " --use-set-session-authorization"); + if (security_label) + appendPQExpBuffer(pgdumpopts, " --security-label"); + if (security_acl) + appendPQExpBuffer(pgdumpopts, " --security-acl"); if (optind < argc) { @@ -403,6 +416,19 @@ main(int argc, char *argv[]) } } + if (security_label > 0) + { + PGresult *res + = PQexec(conn, "SHOW sepostgresql"); + if (PQresultStatus(res) != PGRES_TUPLES_OK || + PQntuples(res) != 1 || + strcmp(PQgetvalue(res, 0, 0), "on") != 0) + { + fprintf(stderr, "SE-PostgreSQL is not available now."); + exit(1); + } + } + /* * Open the output file if required, otherwise use stdout */ @@ -1130,55 +1156,56 @@ dumpCreateDB(PGconn *conn) /* Now collect all the information about databases to dump */ if (server_version >= 80400) - res = executeQuery(conn, - "SELECT datname, " + appendPQExpBuffer(buf, "SELECT datname, " "coalesce(rolname, (select rolname from pg_authid where oid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "datcollate, datctype, datfrozenxid, " "datistemplate, datacl, datconnlimit, " - "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace " + "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace, " + "%s AS security_label " "FROM pg_database d LEFT JOIN pg_authid u ON (datdba = u.oid) " - "WHERE datallowconn ORDER BY 1"); + "WHERE datallowconn ORDER BY 1", + security_label ? "sepgsql_raw_to_trans(datselabel)" : "null::text"); else if (server_version >= 80100) - res = executeQuery(conn, - "SELECT datname, " + appendPQExpBuffer(buf, "SELECT datname, " "coalesce(rolname, (select rolname from pg_authid where oid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "null::text AS datcollate, null::text AS datctype, datfrozenxid, " "datistemplate, datacl, datconnlimit, " - "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace " + "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace, " + "null::text " "FROM pg_database d LEFT JOIN pg_authid u ON (datdba = u.oid) " "WHERE datallowconn ORDER BY 1"); else if (server_version >= 80000) - res = executeQuery(conn, - "SELECT datname, " + appendPQExpBuffer(buf, "SELECT datname, " "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "null::text AS datcollate, null::text AS datctype, datfrozenxid, " "datistemplate, datacl, -1 as datconnlimit, " - "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace " + "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace, " + "null::text " "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) " "WHERE datallowconn ORDER BY 1"); else if (server_version >= 70300) - res = executeQuery(conn, - "SELECT datname, " + appendPQExpBuffer(buf, "SELECT datname, " "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "null::text AS datcollate, null::text AS datctype, datfrozenxid, " "datistemplate, datacl, -1 as datconnlimit, " - "'pg_default' AS dattablespace " + "'pg_default' AS dattablespace, " + "null::text " "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) " "WHERE datallowconn ORDER BY 1"); else if (server_version >= 70100) - res = executeQuery(conn, - "SELECT datname, " + appendPQExpBuffer(buf, "SELECT datname, " "coalesce(" "(select usename from pg_shadow where usesysid=datdba), " "(select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "null::text AS datcollate, null::text AS datctype, 0 AS datfrozenxid, " "datistemplate, '' as datacl, -1 as datconnlimit, " - "'pg_default' AS dattablespace " + "'pg_default' AS dattablespace, " + "null::text " "FROM pg_database d " "WHERE datallowconn ORDER BY 1"); else @@ -1187,18 +1214,20 @@ dumpCreateDB(PGconn *conn) * Note: 7.0 fails to cope with sub-select in COALESCE, so just deal * with getting a NULL by not printing any OWNER clause. */ - res = executeQuery(conn, - "SELECT datname, " + appendPQExpBuffer(buf, "SELECT datname, " "(select usename from pg_shadow where usesysid=datdba), " "pg_encoding_to_char(d.encoding), " "null::text AS datcollate, null::text AS datctype, 0 AS datfrozenxid, " "'f' as datistemplate, " "'' as datacl, -1 as datconnlimit, " - "'pg_default' AS dattablespace " + "'pg_default' AS dattablespace, " + "null::text " "FROM pg_database d " "ORDER BY 1"); } + res = PQexec(conn, buf->data); + for (i = 0; i < PQntuples(res); i++) { char *dbname = PQgetvalue(res, i, 0); @@ -1211,6 +1240,7 @@ dumpCreateDB(PGconn *conn) char *dbacl = PQgetvalue(res, i, 7); char *dbconnlimit = PQgetvalue(res, i, 8); char *dbtablespace = PQgetvalue(res, i, 9); + char *dbseclabel = PQgetvalue(res, i, 9); char *fdbname; fdbname = strdup(fmtId(dbname)); @@ -1266,6 +1296,10 @@ dumpCreateDB(PGconn *conn) appendPQExpBuffer(buf, " CONNECTION LIMIT = %s", dbconnlimit); + if (security_label > 0 && strlen(dbseclabel) > 0) + appendPQExpBuffer(buf, " SECURITY_LABEL = '%s'", + dbseclabel); + appendPQExpBuffer(buf, ";\n"); if (strcmp(dbistemplate, "t") == 0) diff --git a/src/include/access/htup.h b/src/include/access/htup.h index f271cbc..adf12c0 100644 --- a/src/include/access/htup.h +++ b/src/include/access/htup.h @@ -163,7 +163,7 @@ typedef HeapTupleHeaderData *HeapTupleHeader; #define HEAP_HASVARWIDTH 0x0002 /* has variable-width attribute(s) */ #define HEAP_HASEXTERNAL 0x0004 /* has external stored attribute(s) */ #define HEAP_HASOID 0x0008 /* has an object-id field */ -/* bit 0x0010 is available */ +#define HEAP_HASSECID 0x0010 /* has an security-id field */ #define HEAP_COMBOCID 0x0020 /* t_cid is a combo cid */ #define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */ #define HEAP_XMAX_SHARED_LOCK 0x0080 /* xmax is shared locker */ @@ -290,6 +290,9 @@ do { \ (tup)->t_choice.t_datum.datum_typmod = (typmod) \ ) +#define HeapTupleHeaderHasOid(tup) \ + ((tup)->t_infomask & HEAP_HASOID) + #define HeapTupleHeaderGetOid(tup) \ ( \ ((tup)->t_infomask & HEAP_HASOID) ? \ @@ -349,6 +352,25 @@ do { \ (tup)->t_infomask2 = ((tup)->t_infomask2 & ~HEAP_NATTS_MASK) | (natts) \ ) +#define HeapTupleHeaderHasSecid(tup) \ + ((tup)->t_infomask & HEAP_HASSECID) + +#define HeapTupleHeaderGetSecid(tup) \ + ( \ + HeapTupleHeaderHasSecid(tup) \ + ? (*(Oid *)((char *)(tup) + (tup)->t_hoff \ + - (HeapTupleHeaderHasOid(tup) ? sizeof(Oid) : 0) \ + - sizeof(Oid))) \ + : InvalidOid \ + ) + +#define HeapTupleHeaderSetSecid(tup, secid) \ + do { \ + Assert(HeapTupleHeaderHasSecid(tup)); \ + *((Oid *)((char *)(tup) + (tup)->t_hoff \ + - (HeapTupleHeaderHasOid(tup) ? sizeof(Oid) : 0) \ + - sizeof(Oid))) = (secid); \ + } while(0) /* * BITMAPLEN(NATTS) - @@ -549,6 +571,14 @@ typedef HeapTupleData *HeapTuple; #define HeapTupleSetOid(tuple, oid) \ HeapTupleHeaderSetOid((tuple)->t_data, (oid)) +#define HeapTupleHasSecid(tuple) \ + HeapTupleHeaderHasSecid((tuple)->t_data) + +#define HeapTupleGetSecid(tuple) \ + HeapTupleHeaderGetSecid((tuple)->t_data) + +#define HeapTupleSetSecid(tuple, secid) \ + HeapTupleHeaderSetSecid((tuple)->t_data, (secid)) /* * WAL record definitions for heapam.c's WAL operations diff --git a/src/include/access/sysattr.h b/src/include/access/sysattr.h index f8fa910..762219f 100644 --- a/src/include/access/sysattr.h +++ b/src/include/access/sysattr.h @@ -25,7 +25,19 @@ #define MaxTransactionIdAttributeNumber (-5) #define MaxCommandIdAttributeNumber (-6) #define TableOidAttributeNumber (-7) -#define FirstLowInvalidHeapAttributeNumber (-8) +#define SecurityAttributeNumber (-8) +#define FirstLowInvalidHeapAttributeNumber (-9) +/* + * Attribute names for the system-defined attributes + */ +#define SelfItemPointerAttributeName "ctid" +#define ObjectIdAttributeName "oid" +#define MinTransactionIdAttributeName "xmin" +#define MinCommandIdAttributeName "cmin" +#define MaxTransactionIdAttributeName "xmax" +#define MaxCommandIdAttributeName "cmax" +#define TableOidAttributeName "tableoid" +#define SecurityAttributeName "security_context" #endif /* SYSATTR_H */ diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 87c931a..b102e90 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -75,6 +75,7 @@ typedef struct tupleDesc Oid tdtypeid; /* composite type ID for tuple type */ int32 tdtypmod; /* typmod for tuple type */ bool tdhasoid; /* tuple has oid attribute in its header */ + bool tdhassecid; /* tuple has secid attribute in its header */ int tdrefcount; /* reference count, or -1 if not counting */ } *TupleDesc; diff --git a/src/include/bootstrap/bootstrap.h b/src/include/bootstrap/bootstrap.h index b43408f..c4342b2 100644 --- a/src/include/bootstrap/bootstrap.h +++ b/src/include/bootstrap/bootstrap.h @@ -70,7 +70,8 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, - WalWriterProcess + WalWriterProcess, + SelinuxReceiverProcess, } AuxProcType; #endif /* BOOTSTRAP_H */ diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 62b9a52..5c008b9 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -156,6 +156,9 @@ typedef enum ObjectClass extern void performDeletion(const ObjectAddress *object, DropBehavior behavior); +extern void performDeletionNoPerms(const ObjectAddress *object, + DropBehavior behavior); + extern void performMultipleDeletions(const ObjectAddresses *objects, DropBehavior behavior); diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index d618319..e660545 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -56,7 +56,8 @@ extern Oid heap_create_with_catalog(const char *relname, int oidinhcount, OnCommitAction oncommit, Datum reloptions, - bool allow_system_table_mods); + bool allow_system_table_mods, + Oid *secLabels); extern void heap_drop_with_catalog(Oid relid); @@ -68,12 +69,14 @@ extern List *heap_truncate_find_FKs(List *relationIds); extern void InsertPgAttributeTuple(Relation pg_attribute_rel, Form_pg_attribute new_attribute, - CatalogIndexState indstate); + CatalogIndexState indstate, + Oid new_att_secid); extern void InsertPgClassTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, - Datum reloptions); + Datum reloptions, + Oid new_rel_secid); extern List *AddRelationNewConstraints(Relation rel, List *newColDefaults, @@ -103,6 +106,8 @@ extern Form_pg_attribute SystemAttributeDefinition(AttrNumber attno, extern Form_pg_attribute SystemAttributeByName(const char *attname, bool relhasoids); +extern bool SystemAttributeIsWritable(AttrNumber attnum); + extern void CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind); extern void CheckAttributeType(const char *attname, Oid atttypid); diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 0a46611..70024b1 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -252,6 +252,11 @@ DECLARE_UNIQUE_INDEX(pg_type_oid_index, 2703, on pg_type using btree(oid oid_ops DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typname name_ops, typnamespace oid_ops)); #define TypeNameNspIndexId 2704 +DECLARE_UNIQUE_INDEX(pg_security_secid_index, 3401, on pg_security using btree(secid oid_ops, datid oid_ops, relid oid_ops)); +#define SecuritySecidIndexId 3401 +DECLARE_INDEX(pg_security_secattr_index, 3402, on pg_security using btree(datid oid_ops, relid oid_ops, secattr text_ops)); +#define SecuritySecattrIndexId 3402 + DECLARE_UNIQUE_INDEX(pg_foreign_data_wrapper_oid_index, 112, on pg_foreign_data_wrapper using btree(oid oid_ops)); #define ForeignDataWrapperOidIndexId 112 diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index eaa405f..165b54d 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -276,6 +276,7 @@ DATA(insert ( 1247 cmin 29 0 4 -4 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1247 xmax 28 0 4 -5 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1247 cmax 29 0 4 -6 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1247 tableoid 26 0 4 -7 0 -1 -1 t p i t f f t 0 _null_)); +DATA(insert ( 1247 security_context 25 0 -1 -8 0 -1 -1 f x i t f f t 0 _null_)); /* ---------------- * pg_proc @@ -340,6 +341,7 @@ DATA(insert ( 1255 cmin 29 0 4 -4 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1255 xmax 28 0 4 -5 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1255 cmax 29 0 4 -6 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1255 tableoid 26 0 4 -7 0 -1 -1 t p i t f f t 0 _null_)); +DATA(insert ( 1255 security_context 25 0 -1 -8 0 -1 -1 f x i t f f t 0 _null_)); /* ---------------- * pg_attribute @@ -390,6 +392,7 @@ DATA(insert ( 1249 cmin 29 0 4 -4 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1249 xmax 28 0 4 -5 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1249 cmax 29 0 4 -6 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1249 tableoid 26 0 4 -7 0 -1 -1 t p i t f f t 0 _null_)); +DATA(insert ( 1249 security_context 25 0 -1 -8 0 -1 -1 f x i t f f t 0 _null_)); /* ---------------- * pg_class @@ -454,6 +457,7 @@ DATA(insert ( 1259 cmin 29 0 4 -4 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1259 xmax 28 0 4 -5 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1259 cmax 29 0 4 -6 0 -1 -1 t p i t f f t 0 _null_)); DATA(insert ( 1259 tableoid 26 0 4 -7 0 -1 -1 t p i t f f t 0 _null_)); +DATA(insert ( 1259 security_context 25 0 -1 -8 0 -1 -1 f x i t f f t 0 _null_)); /* ---------------- * pg_index diff --git a/src/include/catalog/pg_conversion_fn.h b/src/include/catalog/pg_conversion_fn.h index 83cf657..eecebd3 100644 --- a/src/include/catalog/pg_conversion_fn.h +++ b/src/include/catalog/pg_conversion_fn.h @@ -17,7 +17,7 @@ extern Oid ConversionCreate(const char *conname, Oid connamespace, Oid conowner, int32 conforencoding, int32 contoencoding, - Oid conproc, bool def); + Oid conproc, Oid consecid, bool def); extern void RemoveConversionById(Oid conversionOid); extern Oid FindConversion(const char *conname, Oid connamespace); extern Oid FindDefaultConversion(Oid connamespace, int32 for_encoding, int32 to_encoding); diff --git a/src/include/catalog/pg_largeobject.h b/src/include/catalog/pg_largeobject.h index 6dd2fb0..b84c0d2 100644 --- a/src/include/catalog/pg_largeobject.h +++ b/src/include/catalog/pg_largeobject.h @@ -51,7 +51,7 @@ typedef FormData_pg_largeobject *Form_pg_largeobject; #define Anum_pg_largeobject_pageno 2 #define Anum_pg_largeobject_data 3 -extern Oid LargeObjectCreate(Oid loid); +extern Oid LargeObjectCreate(Oid loid, Oid secid); extern void LargeObjectDrop(Oid loid); extern void LargeObjectAlterOwner(Oid loid, Oid newOwnerId); extern bool LargeObjectExists(Oid loid); diff --git a/src/include/catalog/pg_namespace.h b/src/include/catalog/pg_namespace.h index 9168079..7e8487a 100644 --- a/src/include/catalog/pg_namespace.h +++ b/src/include/catalog/pg_namespace.h @@ -77,6 +77,6 @@ DESCR("standard public schema"); /* * prototypes for functions in pg_namespace.c */ -extern Oid NamespaceCreate(const char *nspName, Oid ownerId); +extern Oid NamespaceCreate(const char *nspName, Oid ownerId, Oid nspsecid); #endif /* PG_NAMESPACE_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 0285acd..c6a5247 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4335,6 +4335,19 @@ DESCR("I/O"); DATA(insert OID = 2963 ( uuid_hash PGNSP PGUID 12 1 0 0 f f f t f i 1 0 23 "2950" _null_ _null_ _null_ _null_ uuid_hash _null_ _null_ _null_ )); DESCR("hash"); +/* SE-PostgreSQL related functions */ +DATA(insert OID = 3415 ( seclabel_to_secid PGNSP PGUID 12 1 0 0 f f f t f v 1 0 26 "2249" _null_ _null_ _null_ _null_ seclabel_to_secid _null_ _null_ _null_ )); +DATA(insert OID = 3416 ( sepgsql_getcon PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ sepgsql_getcon _null_ _null_ _null_ )); +DATA(insert OID = 3417 ( sepgsql_server_getcon PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ sepgsql_server_getcon _null_ _null_ _null_ )); +DATA(insert OID = 3418 ( sepgsql_get_user PGNSP PGUID 12 1 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ sepgsql_get_user _null_ _null_ _null_ )); +DATA(insert OID = 3419 ( sepgsql_set_user PGNSP PGUID 12 1 0 0 f f f t f v 2 0 25 "25 25" _null_ _null_ _null_ _null_ sepgsql_set_user _null_ _null_ _null_ )); +DATA(insert OID = 3420 ( sepgsql_get_role PGNSP PGUID 12 1 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ sepgsql_get_role _null_ _null_ _null_ )); +DATA(insert OID = 3421 ( sepgsql_set_role PGNSP PGUID 12 1 0 0 f f f t f v 2 0 25 "25 25" _null_ _null_ _null_ _null_ sepgsql_set_role _null_ _null_ _null_ )); +DATA(insert OID = 3422 ( sepgsql_get_type PGNSP PGUID 12 1 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ sepgsql_get_type _null_ _null_ _null_ )); +DATA(insert OID = 3423 ( sepgsql_set_type PGNSP PGUID 12 1 0 0 f f f t f v 2 0 25 "25 25" _null_ _null_ _null_ _null_ sepgsql_set_type _null_ _null_ _null_ )); +DATA(insert OID = 3424 ( sepgsql_get_range PGNSP PGUID 12 1 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ sepgsql_get_range _null_ _null_ _null_ )); +DATA(insert OID = 3425 ( sepgsql_set_range PGNSP PGUID 12 1 0 0 f f f t f v 2 0 25 "25 25" _null_ _null_ _null_ _null_ sepgsql_set_range _null_ _null_ _null_ )); + /* enum related procs */ DATA(insert OID = 3504 ( anyenum_in PGNSP PGUID 12 1 0 0 f f f t f i 1 0 3500 "2275" _null_ _null_ _null_ _null_ anyenum_in _null_ _null_ _null_ )); DESCR("I/O"); diff --git a/src/include/catalog/pg_proc_fn.h b/src/include/catalog/pg_proc_fn.h index e3453f2..c25af6b 100644 --- a/src/include/catalog/pg_proc_fn.h +++ b/src/include/catalog/pg_proc_fn.h @@ -37,7 +37,8 @@ extern Oid ProcedureCreate(const char *procedureName, List *parameterDefaults, Datum proconfig, float4 procost, - float4 prorows); + float4 prorows, + Node *proseclabel); extern bool function_parse_error_transpose(const char *prosrc); diff --git a/src/include/catalog/pg_security.h b/src/include/catalog/pg_security.h new file mode 100644 index 0000000..973df01 --- /dev/null +++ b/src/include/catalog/pg_security.h @@ -0,0 +1,89 @@ +/* + * src/include/catalog/pg_security.h + * Definition of the security label relation (pg_security) + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#ifndef PG_SECURITY_H +#define PG_SECURITY_H + +#include "catalog/genbki.h" + +#include "access/htup.h" +#include "nodes/parsenodes.h" +#include "utils/acl.h" +#include "utils/relcache.h" + +#define SecurityRelationId 3400 + +CATALOG(pg_security,3400) BKI_SHARED_RELATION BKI_WITHOUT_OIDS +{ + /* Identifier of the security attribute */ + Oid secid; + + /* OID of the database which referes the entry */ + Oid datid; + + /* OID of the table which refers the entry */ + Oid relid; + + /* Text representation of security attribute */ + text secattr; +} FormData_pg_security; + +/* + * Form_pg_security corresponds to a pointer to a tuple with + * the format of pg_security relation. + */ +typedef FormData_pg_security *Form_pg_security; + +/* + * Compiler constants for pg_security + */ +#define Natts_pg_security 4 +#define Anum_pg_security_secid 1 +#define Anum_pg_security_datid 2 +#define Anum_pg_security_relid 3 +#define Anum_pg_security_secattr 4 + +/* + * Functions to translate between security label and identifier + */ +extern void +securityPostBootstrapingMode(void); + +extern void +securityOnCreateDatabase(Oid src_datid, Oid dst_datid); + +extern void +securityOnDropDatabase(Oid datid); + +extern bool +securityTupleDescHasSecid(Oid relid, char relkind); + +extern Oid +securityRawSecLabelIn(Oid relid, char *seclabel); + +extern char * +securityRawSecLabelOut(Oid relid, Oid secid); + +extern Oid +securityTransSecLabelIn(Oid relid, char *seclabel); + +extern char * +securityTransSecLabelOut(Oid relid, Oid secid); + +extern Datum +securitySysattSecLabelOut(Oid relid, HeapTuple tuple); + +extern void +securityReclaimOnDropTable(Oid relid); + +extern void +seclabelRelationReclaim(Oid relOid); + +extern Datum +seclabel_to_secid(PG_FUNCTION_ARGS); + +#endif /* PG_SECURITY_H */ diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h index bd6e0cf..b7c39f0 100644 --- a/src/include/catalog/toasting.h +++ b/src/include/catalog/toasting.h @@ -58,5 +58,8 @@ DECLARE_TOAST(pg_database, 2844, 2845); DECLARE_TOAST(pg_shdescription, 2846, 2847); #define PgShdescriptionToastTable 2846 #define PgShdescriptionToastIndex 2847 +DECLARE_TOAST(pg_security, 3403, 3404); +#define PgSecurityToastTable 3403 +#define PgSecurityToastIndex 3404 #endif /* TOASTING_H */ diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h index ab5d6f4..c332b71 100644 --- a/src/include/commands/alter.h +++ b/src/include/commands/alter.h @@ -19,5 +19,6 @@ extern void ExecRenameStmt(RenameStmt *stmt); extern void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt); extern void ExecAlterOwnerStmt(AlterOwnerStmt *stmt); +extern void ExecAlterSecLabelStmt(AlterSecLabelStmt *stmt); #endif /* ALTER_H */ diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h index 0ec23d7..3980056 100644 --- a/src/include/commands/dbcommands.h +++ b/src/include/commands/dbcommands.h @@ -58,6 +58,7 @@ extern void RenameDatabase(const char *oldname, const char *newname); extern void AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel); extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt); extern void AlterDatabaseOwner(const char *dbname, Oid newOwnerId); +extern void AlterDatabaseSecLabel(const char *dbname, DefElem *seclabel); extern Oid get_database_oid(const char *dbname); extern char *get_database_name(Oid dbid); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 4356492..8f903af 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -53,6 +53,7 @@ extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType); extern void RenameFunction(List *name, List *argtypes, const char *newname); extern void AlterFunctionOwner(List *name, List *argtypes, Oid newOwnerId); extern void AlterFunctionOwner_oid(Oid procOid, Oid newOwnerId); +extern void AlterFunctionSecLabel(List *name, List *argtypes, DefElem *seclabel); extern void AlterFunction(AlterFunctionStmt *stmt); extern void CreateCast(CreateCastStmt *stmt); extern void DropCast(DropCastStmt *stmt); diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h index 5f384a1..1fc113b 100644 --- a/src/include/commands/schemacmds.h +++ b/src/include/commands/schemacmds.h @@ -26,5 +26,6 @@ extern void RemoveSchemaById(Oid schemaOid); extern void RenameSchema(const char *oldname, const char *newname); extern void AlterSchemaOwner(const char *name, Oid newOwnerId); extern void AlterSchemaOwner_oid(Oid schemaOid, Oid newOwnerId); +extern void AlterSchemaSecLabel(const char *name, DefElem *seclabel); #endif /* SCHEMACMDS_H */ diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 21b067c..2ac7b3c 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -35,6 +35,9 @@ extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid, Oid oldNspOid, Oid newNspOid, bool hasDependEntry); +extern void AlterRelationSecLabel(RangeVar *relation, const char *attname, + ObjectType objtype, DefElem *seclabel); + extern void CheckTableNotInUse(Relation rel, const char *stmt); extern void ExecuteTruncate(TruncateStmt *stmt); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 43395e9..09d6148 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -130,7 +130,7 @@ extern TupleHashEntry FindTupleHashEntry(TupleHashTable hashtable, /* * prototypes from functions in execJunk.c */ -extern JunkFilter *ExecInitJunkFilter(List *targetList, bool hasoid, +extern JunkFilter *ExecInitJunkFilter(List *targetList, bool hasoid, bool hasseclabel, TupleTableSlot *slot); extern JunkFilter *ExecInitJunkFilterConversion(List *targetList, TupleDesc cleanTupType, @@ -163,6 +163,7 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, bool doInstrument); extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids); +extern bool ExecContextForcesSecids(PlanState *planstate, bool *hassecids); extern void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti, @@ -216,8 +217,8 @@ extern void ExecInitScanTupleSlot(EState *estate, ScanState *scanstate); extern TupleTableSlot *ExecInitExtraTupleSlot(EState *estate); extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate, TupleDesc tupType); -extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid); -extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid); +extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid, bool hasseclabel); +extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid, bool hasseclabel); extern TupleDesc ExecTypeFromExprList(List *exprList); extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg); diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h index e40082d..7fd299c 100644 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -127,6 +127,7 @@ typedef struct TupleTableSlot MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */ HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only case */ long tts_off; /* saved state for slot_deform_tuple */ + Datum tts_seclabel; /* temp storage for the given security_label */ } TupleTableSlot; #define TTS_HAS_PHYSICAL_TUPLE(slot) \ diff --git a/src/include/libpq/be-fsstubs.h b/src/include/libpq/be-fsstubs.h index 862b014..03ca71b 100644 --- a/src/include/libpq/be-fsstubs.h +++ b/src/include/libpq/be-fsstubs.h @@ -37,6 +37,9 @@ extern Datum lo_tell(PG_FUNCTION_ARGS); extern Datum lo_unlink(PG_FUNCTION_ARGS); extern Datum lo_truncate(PG_FUNCTION_ARGS); +extern Datum lo_get_security(PG_FUNCTION_ARGS); +extern Datum lo_set_security(PG_FUNCTION_ARGS); + /* * compatibility option for access control */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 925375b..0285a39 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -337,6 +337,7 @@ typedef enum NodeTag T_CreateUserMappingStmt, T_AlterUserMappingStmt, T_DropUserMappingStmt, + T_AlterSecLabelStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 487a226..cdb49d4 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -463,6 +463,7 @@ typedef struct ColumnDef Node *raw_default; /* default value (untransformed parse tree) */ Node *cooked_default; /* default value (transformed expr tree) */ List *constraints; /* other constraints on column */ + Node *secLabel; /* security label of column */ } ColumnDef; /* @@ -1069,6 +1070,7 @@ typedef struct CreateSchemaStmt NodeTag type; char *schemaname; /* the name of the schema to create */ char *authid; /* the owner of the created schema */ + Node *secLabel; /* explicitly specified security label */ List *schemaElts; /* schema components (list of parsenodes) */ } CreateSchemaStmt; @@ -1335,6 +1337,7 @@ typedef struct CreateStmt List *options; /* options from WITH clause */ OnCommitAction oncommit; /* what do we do at COMMIT? */ char *tablespacename; /* table space to use, or NULL */ + List *secLabel; /* explicitly specified security label */ } CreateStmt; /* ---------- @@ -1639,6 +1642,7 @@ typedef struct CreateSeqStmt NodeTag type; RangeVar *sequence; /* the sequence to create */ List *options; + Node *secLabel; } CreateSeqStmt; typedef struct AlterSeqStmt @@ -1993,6 +1997,20 @@ typedef struct AlterOwnerStmt char *newowner; /* the new owner */ } AlterOwnerStmt; +/* ---------------------- + * Alter Object Security Label Statement + * ---------------------- + */ +typedef struct AlterSecLabelStmt +{ + NodeTag type; + ObjectType objectType; /* OBJECT_TABLE, OBJECT_COLUMN, etc */ + RangeVar *relation; /* in case it's a table */ + List *object; /* in case it's some other object */ + List *objarg; /* argument types, if applicable */ + char *subname; /* column name, if needed */ + Node *secLabel; /* the new security label */ +} AlterSecLabelStmt; /* ---------------------- * Create Rule Statement diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 23a5117..2525e22 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -16,6 +16,7 @@ #include "access/sdir.h" #include "nodes/bitmapset.h" +#include "nodes/parsenodes.h" #include "nodes/primnodes.h" #include "storage/itemptr.h" @@ -239,6 +240,12 @@ typedef struct Scan { Plan plan; Index scanrelid; /* relid is index into the range table */ + + /* + * Row-level access control stuff. Zero means we don't need + * to apply row-level access control on the Scan. + */ + uint32 rowlvPerms; } Scan; /* ---------------- diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index ea48889..6133c38 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -383,6 +383,15 @@ typedef struct RelOptInfo * list just to avoid recomputing the best inner indexscan repeatedly for * similar outer relations. See comments for InnerIndexscanInfo. */ + + /* + * Permissions used in Row-level access control features both of DAC + * and MAC. The lower 16bit is used for DAC, and rest of upper bits + * are used for MAC. When rowlvPerms is zero, so it means we don't need + * to apply the row-level stuff on the relation in both of levels. + * It can be used as a hint for optimization stuff. + */ + uint32 rowlvPerms; } RelOptInfo; /* diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 67e9cb4..df9bb5b 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -88,6 +88,7 @@ PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD) PG_KEYWORD("constraint", CONSTRAINT, RESERVED_KEYWORD) PG_KEYWORD("constraints", CONSTRAINTS, UNRESERVED_KEYWORD) PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD) +PG_KEYWORD("context", CONTEXT_P, UNRESERVED_KEYWORD) PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD) PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD) PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD) diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 3473227..28301d0 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -263,6 +263,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LDAP_H +/* Define to 1 if you have the `audit' library (-laudit). */ +#undef HAVE_LIBAUDIT + /* Define to 1 if you have the `crypto' library (-lcrypto). */ #undef HAVE_LIBCRYPTO @@ -391,6 +394,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SECURITY_PAM_APPL_H +/* Define to 1 if you enable SELinux support */ +#undef HAVE_SELINUX + /* Define to 1 if you have the `setproctitle' function. */ #undef HAVE_SETPROCTITLE diff --git a/src/include/security/rowlevel.h b/src/include/security/rowlevel.h new file mode 100644 index 0000000..a737a0d --- /dev/null +++ b/src/include/security/rowlevel.h @@ -0,0 +1,44 @@ +/* + * src/include/security/rowlevel.h + * Definition of the facility of row-level access controls + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#ifndef ROWLEVEL_H +#define ROWLEVEL_H + +#include "access/htup.h" +#include "executor/tuptable.h" +#include "nodes/plannodes.h" +#include "utils/relcache.h" + +#define ROWLV_BYPASS_MODE 1 +#define ROWLV_FILTER_MODE 2 +#define ROWLV_ABORT_MODE 3 + +extern int +rowlvGetPerformingMode(void); + +extern int +rowlvSetPerformingMode(int mode); + +extern uint32 +rowlvSetupPermissions(RangeTblEntry *rte); + +extern bool +rowlvExecScanFilter(Scan *scan, Relation rel, TupleTableSlot *slot); + +extern void +rowlvExecScanAbort(Scan *scan, Relation rel, TupleTableSlot *slot); + +extern void +rowlvHeapTupleInsert(Relation rel, HeapTuple newtup, bool internal); + +extern void +rowlvHeapTupleUpdate(Relation rel, ItemPointer otid, HeapTuple newtup); + +extern bool +rowlvCopyToTuple(Relation rel, HeapTuple tuple); + +#endif /* ROWLEVEL_H */ diff --git a/src/include/security/sepgsql.h b/src/include/security/sepgsql.h new file mode 100644 index 0000000..d5ac80b --- /dev/null +++ b/src/include/security/sepgsql.h @@ -0,0 +1,725 @@ +/* + * src/include/security/sepgsql.h + * Headers of SE-PostgreSQL + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#ifndef SEPGSQL_H +#define SEPGSQL_H + +#include "access/htup.h" +#include "catalog/dependency.h" +#include "executor/execdesc.h" +#include "fmgr.h" +#include "nodes/parsenodes.h" +#include "storage/large_object.h" +#include "utils/relcache.h" + +#ifdef HAVE_SELINUX + +#include + +/* GUC parameter to turn on/off SE-PostgreSQL */ +extern int sepostgresql_mode; + +#define SEPGSQL_MODE_DEFAULT 1 +#define SEPGSQL_MODE_ENFORCING 2 +#define SEPGSQL_MODE_PERMISSIVE 3 +#define SEPGSQL_MODE_INTERNAL 4 +#define SEPGSQL_MODE_DISABLED 5 + +/* GUC parameter to turn on/off Row-level controls */ +extern bool sepostgresql_row_level; + +/* GUC parameter to turn on/off mcstrans */ +extern bool sepostgresql_mcstrans; + +/* Objject classes and permissions internally used */ +enum SepgsqlClasses +{ + SEPG_CLASS_PROCESS = 0, + SEPG_CLASS_FILE, + SEPG_CLASS_DIR, + SEPG_CLASS_LNK_FILE, + SEPG_CLASS_CHR_FILE, + SEPG_CLASS_BLK_FILE, + SEPG_CLASS_SOCK_FILE, + SEPG_CLASS_FIFO_FILE, + SEPG_CLASS_DB_DATABASE, + SEPG_CLASS_DB_SCHEMA, + SEPG_CLASS_DB_TABLE, + SEPG_CLASS_DB_VIEW, + SEPG_CLASS_DB_SEQUENCE, + SEPG_CLASS_DB_PROCEDURE, + SEPG_CLASS_DB_COLUMN, + SEPG_CLASS_DB_TUPLE, + SEPG_CLASS_DB_BLOB, + SEPG_CLASS_MAX, +}; + +#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_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_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_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_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_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_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_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__REFERENCE (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_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__USAGE (1<<6) + +#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_COLUMN__REFERENCE (1<<9) + +#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) + +/* + * sepgsql_sid_t : alternative representation of security context + */ +typedef struct { + Oid relid; + Oid secid; +} sepgsql_sid_t; + +#define SidIsValid(sid) (OidIsValid((sid).relid) && OidIsValid((sid).secid)) + +/* + * selinux.c : communication to in-kernel SELinux + */ +extern void sepgsqlInitialize(void); +extern Size sepgsqlShmemSize(void); +extern bool sepgsqlIsEnabled(void); +extern bool sepgsqlIsEnabledBootstrap(void); +extern bool sepgsqlGetEnforce(void); +extern char *sepgsqlShowMode(void); +extern char *sepgsqlGetServerLabel(void); +extern char *sepgsqlGetClientLabel(void); +extern char *sepgsqlSetClientLabel(char *new_label); +extern bool +sepgsqlComputePerms(char *scontext, char *tcontext, + uint16 tclass, uint32 required, + const char *audit_name, bool abort); +extern char * +sepgsqlComputeCreate(char *scontext, char *tcontext, uint16 tclass); +extern bool +sepgsqlClientHasPerms(sepgsql_sid_t tsid, uint16 tclass, uint32 required, + const char *audit_name, bool abort); +extern sepgsql_sid_t +sepgsqlClientCreateSecid(sepgsql_sid_t tsid, uint16 tclass, Oid nrelid); +extern char * +sepgsqlClientCreateLabel(sepgsql_sid_t tsid, uint16 tclass); + +extern bool sepgsqlReceiverStart(void); +extern void sepgsqlReceiverMain(void); + +/* + * bridge.c : new style security hooks + */ + +/* pg_attribute */ +extern Oid +sepgsql_attribute_create(Oid relOid, ColumnDef *cdef); +extern void +sepgsql_attribute_alter(Oid relOid, const char *attname); +extern void +sepgsql_attribute_drop(Oid relOid, AttrNumber attnum); +extern void +sepgsql_attribute_grant(Oid relOid, AttrNumber attnum); +extern Oid +sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum, DefElem *newLabel); + +/* pg_cast */ +extern Oid +sepgsql_cast_create(Oid sourceTypOid, Oid targetTypOid, Oid funcOid); +extern void +sepgsql_cast_drop(Oid castOid); + +/* pg_class */ +extern Oid * +sepgsql_relation_create(const char *relName, + char relkind, + TupleDesc tupDesc, + Oid nspOid, + DefElem *relLabel, + List *colList, + bool createAs, + bool permission); +extern Oid * +sepgsql_relation_copy(Relation src); +extern void +sepgsql_relation_alter(Oid relOid, const char *newName, Oid newNsp); +extern void +sepgsql_relation_drop(Oid relOid); +extern void +sepgsql_relation_grant(Oid relOid); +extern Oid +sepgsql_relation_relabel(Oid relOid, DefElem *newLabel); +extern void +sepgsql_relation_get_transaction_id(Oid relOid); +extern void +sepgsql_relation_copy_definition(Oid relOid); +extern void +sepgsql_relation_truncate(Relation rel); +extern void +sepgsql_relation_references(Relation rel, int16 *attnums, int natts); +extern void +sepgsql_relation_lock(Oid relOid); +extern void +sepgsql_view_replace(Oid viewOid); +extern void +sepgsql_index_create(Oid relOid, Oid nspOid); +extern void +sepgsql_sequence_get_value(Oid seqOid); +extern void +sepgsql_sequence_next_value(Oid seqOid); +extern void +sepgsql_sequence_set_value(Oid seqOid); + +/* pg_conversion */ +extern Oid +sepgsql_conversion_create(const char *convName, Oid nspOid, Oid procOid); +extern void +sepgsql_conversion_alter(Oid convOid, const char *newName); +extern void +sepgsql_conversion_drop(Oid convOid); + +/* pg_database */ +extern Oid +sepgsql_database_create(const char *datName, Oid srcDatOid, DefElem *newLabel); +extern void +sepgsql_database_alter(Oid datOid); +extern void +sepgsql_database_drop(Oid datOid); +extern Oid +sepgsql_database_relabel(Oid datOid, DefElem *newLabel); +extern void +sepgsql_database_grant(Oid datOid); +extern void +sepgsql_database_access(Oid datOid); +extern bool +sepgsql_database_superuser(Oid datOid); +extern void +sepgsql_database_load_module(Oid datOid, const char *filename); + +/* pg_foreign_data_wrapper */ +extern Oid +sepgsql_fdw_create(const char *fdwName, Oid fdwValidator); +extern void +sepgsql_fdw_alter(Oid fdwOid, Oid newValidator); +extern void +sepgsql_fdw_drop(Oid fdwOid); +extern void +sepgsql_fdw_grant(Oid fdwOid); + +/* pg_foreign_server */ +extern Oid +sepgsql_foreign_server_create(const char *fsrvName); +extern void +sepgsql_foreign_server_alter(Oid fsrvOid); +extern void +sepgsql_foreign_server_drop(Oid fsrvOid); +extern void +sepgsql_foreign_server_grant(Oid fsrvOid); + +/* pg_language */ +extern Oid +sepgsql_language_create(const char *langName, Oid handlerOid, Oid validatorOid); +extern void +sepgsql_language_alter(Oid langOid); +extern void +sepgsql_language_drop(Oid langOid); +extern void +sepgsql_language_grant(Oid langOid); + +/* pg_largeobject */ +extern Oid +sepgsql_largeobject_create(Oid loid, Value *secLabel); +extern void +sepgsql_largeobject_alter(Oid loid); +extern void +sepgsql_largeobject_relabel(Oid loid, Value *secLabel); +extern void +sepgsql_largeobject_drop(Oid loid); +extern void +sepgsql_largeobject_read(Oid loid, Snapshot snapshot); +extern void +sepgsql_largeobject_write(Oid loid, Snapshot snapshot); +extern void +sepgsql_largeobject_export(Oid loid, const char *filename); +extern Oid +sepgsql_largeobject_import(Oid loid, const char *filename); + +/* pg_namespace */ +extern Oid +sepgsql_schema_create(const char *nspName, bool isTemp, DefElem *newLabel); +extern void +sepgsql_schema_alter(Oid nspOid); +extern void +sepgsql_schema_drop(Oid nspOid); +extern Oid +sepgsql_schema_relabel(Oid nspOid, DefElem *newLabel); +extern void +sepgsql_schema_grant(Oid nspOid); +extern bool +sepgsql_schema_search(Oid nspOid, bool abort); + +/* pg_opclass */ +extern Oid +sepgsql_opclass_create(const char *opcName, Oid nspOid); +extern void +sepgsql_opclass_alter(Oid opcOid, const char *newName); +extern void +sepgsql_opclass_drop(Oid opcOid); + +/* pg_opfamily */ +extern Oid +sepgsql_opfamily_create(const char *opfName, Oid nspOid); +extern void +sepgsql_opfamily_alter(Oid opfOid, const char *newName); +extern void +sepgsql_opfamily_drop(Oid opfOid); +extern void +sepgsql_opfamily_add_operator(Oid opfOid, Oid operOid); +extern void +sepgsql_opfamily_add_procedure(Oid opfOid, Oid procOid); + +/* pg_operator */ +extern Oid +sepgsql_operator_create(const char *oprName, Oid oprOid, Oid nspOid, + Oid codeFn, Oid restFn, Oid joinFn); +extern void +sepgsql_operator_alter(Oid oprOid); +extern void +sepgsql_operator_drop(Oid oprOid); + +/* pg_proc */ +extern Oid +sepgsql_proc_create(const char *procName, HeapTuple oldTup, + Oid nspOid, Oid langOid, DefElem *newLabel); +extern void +sepgsql_proc_alter(Oid procOid, const char *newName, Oid newNsp); +extern void +sepgsql_proc_drop(Oid procOid); +extern Oid +sepgsql_proc_relabel(Oid procOid, DefElem *newLabel); +extern void +sepgsql_proc_grant(Oid procOid); +extern void +sepgsql_proc_execute(Oid procOid); +extern bool +sepgsql_proc_hint_inlined(HeapTuple protup); +extern bool +sepgsql_proc_entrypoint(HeapTuple protup); +extern char * +sepgsql_proc_trusted(HeapTuple protup, MemoryContext mcxt); + +/* pg_rewrite */ +extern void +sepgsql_rule_create(Oid relOid, const char *ruleName); +extern void +sepgsql_rule_drop(Oid relOid, const char *ruleName); + +/* pg_trigger */ +extern void +sepgsql_trigger_create(Oid relOid, const char *trigName, Oid procOid); +extern void +sepgsql_trigger_alter(Oid relOid, const char *trigName); +extern void +sepgsql_trigger_drop(Oid relOid, const char *trigName); + +/* pg_ts_config */ +extern Oid +sepgsql_ts_config_create(const char *cfgName, Oid nspOid); +extern void +sepgsql_ts_config_alter(Oid cfgOid, const char *newName); +extern void +sepgsql_ts_config_drop(Oid cfgOid); + +/* pg_ts_dict */ +extern Oid +sepgsql_ts_dict_create(const char *dictName, Oid nspOid); +extern void +sepgsql_ts_dict_alter(Oid dictOid, const char *newName); +extern void +sepgsql_ts_dict_drop(Oid dictOid); + +/* pg_ts_parser */ +extern Oid +sepgsql_ts_parser_create(const char *prsName, Oid nspOid, + Oid startFn, Oid tokenFn, Oid sendFn, + Oid headlineFn, Oid lextypeFn); +extern void +sepgsql_ts_parser_alter(Oid prsOid, const char *newName); +extern void +sepgsql_ts_parser_drop(Oid prsOid); + +/* pg_ts_templace */ +extern Oid +sepgsql_ts_template_create(const char *tmplName, Oid nspOid, + Oid initFn, Oid lexizeFn); +extern void +sepgsql_ts_template_alter(Oid tmplOid, const char *newName); +extern void +sepgsql_ts_template_drop(Oid tmplOid); + +/* pg_type */ +extern Oid +sepgsql_type_create(const char *typName, HeapTuple oldTup, Oid nspOid, + Oid inputProc, Oid outputProc, Oid recvProc, Oid sendProc, + Oid modinProc, Oid modoutProc, Oid analyzeProc); +extern void +sepgsql_type_alter(Oid typOid, const char *newName, Oid newNsp); +extern void +sepgsql_type_drop(Oid typOid); + +/* misc objects */ +extern void +sepgsql_sysobj_drop(const ObjectAddress *object); + +/* filesystem objects */ +void +sepgsql_file_stat(const char *filename); +void +sepgsql_file_read(const char *filename); +void +sepgsql_file_write(const char *filename); + +/* + * checker.c : check permission on given queries + */ +extern void +sepgsqlCheckRTEPerms(RangeTblEntry *rte); + +extern void +sepgsqlCheckCopyTable(Relation rel, List *attnumlist, bool is_from); + +extern void +sepgsqlCheckSelectInto(Oid relaionId); + +extern bool +sepgsqlExecScan(Relation rel, HeapTuple tuple, uint32 required, bool abort); + +extern uint32 +sepgsqlSetupTuplePerms(RangeTblEntry *rte); + +extern void +sepgsqlHeapTupleInsert(Relation rel, HeapTuple newtup, bool internal); + +extern void +sepgsqlHeapTupleUpdate(Relation rel, ItemPointer otid, HeapTuple newtup); + +/* + * label.c : security label management + */ +extern bool sepgsqlTupleDescHasSecid(Oid relid, char relkind); + +extern void sepgsqlPostBootstrapingMode(void); + +extern void sepgsqlSetDefaultSecid(Relation rel, HeapTuple tuple); +extern sepgsql_sid_t sepgsqlGetDefaultDatabaseSecid(Oid src_database_oid); +extern sepgsql_sid_t sepgsqlGetDefaultSchemaSecid(Oid database_oid); +extern sepgsql_sid_t sepgsqlGetDefaultSchemaTempSecid(Oid database_oid); +extern sepgsql_sid_t sepgsqlGetDefaultTableSecid(Oid namespace_oid); +extern sepgsql_sid_t sepgsqlGetDefaultSequenceSecid(Oid namespace_oid); +extern sepgsql_sid_t sepgsqlGetDefaultProcedureSecid(Oid namespace_oid); +extern sepgsql_sid_t sepgsqlGetDefaultColumnSecid(Oid table_oid); +extern sepgsql_sid_t sepgsqlGetDefaultTupleSecid(Oid table_oid); +extern sepgsql_sid_t sepgsqlGetDefaultBlobSecid(Oid database_oid); + +extern Oid *sepgsqlCreateTableColumns(CreateStmt *stmt, + const char *relname, Oid namespace_oid, + TupleDesc tupdesc, char relkind); +extern Oid *sepgsqlCopyTableColumns(Relation source); + +extern sepgsql_sid_t +sepgsqlGetTupleSecid(Oid tableOid, HeapTuple tuple, uint16 *tclass); +extern sepgsql_sid_t +sepgsqlGetSysobjSecid(Oid tableOid, Oid objectId, int32 objsubId, uint16 *tclass); + +extern char *sepgsqlTransSecLabelIn(char *seclabel); +extern char *sepgsqlTransSecLabelOut(char *seclabel); +extern char *sepgsqlRawSecLabelIn(char *seclabel); +extern char *sepgsqlRawSecLabelOut(char *seclabel); +extern char *sepgsqlSysattSecLabelOut(Oid relid, HeapTuple tuple); + +#else /* HAVE_SELINUX */ + +/* avc.c */ +#define sepgsqlShmemSize() (0) + +/* checker.c */ +#define sepgsqlCheckRTEPerms(a) do {} while(0) +#define sepgsqlCheckCopyTable(a,b,c) do {} while(0) +#define sepgsqlCheckSelectInto(a) do {} while(0) +#define sepgsqlExecScan(a,b,c) (true) +#define sepgsqlSetupTuplePerms(a) (0) +#define sepgsqlHeapTupleInsert(a,b,c) do {} while(0) +#define sepgsqlHeapTupleUpdate(a,b,c) do {} while(0) + +/* core.c */ +#define sepgsqlIsEnabled() (false) +#define sepgsqlInitialize() do {} while(0) + +/* bridge.c */ +#define sepgsql_attribute_create(a,b) (InvalidOid) +#define sepgsql_attribute_alter(a,b) do {} while(0) +#define sepgsql_attribute_drop(a,b) do {} while(0) +#define sepgsql_attribute_grant(a,b) do {} while(0) +#define sepgsql_attribute_relabel(a,b,c) (InvalidOid) + +#define sepgsql_cast_create(a,b,c) (InvalidOid) +#define sepgsql_cast_drop(a) (InvalidOid) + +#define sepgsql_relation_create(a,b,c,d,e,f) (NULL) +#define sepgsql_relation_copy(a) (NULL) +#define sepgsql_relation_alter(a,b,c) do {} while(0) +#define sepgsql_relation_drop(a) do {} while(0) +#define sepgsql_relation_grant(a) do {} while(0) +#define sepgsql_relation_relabel(a,b) do {} while(0) +#define sepgsql_relation_get_transaction_id(a) do {} while(0) +#define sepgsql_relation_copy_definition(a) do {} while(0) +#define sepgsql_relation_truncate(a) do {} while(0) +#define sepgsql_relation_references(a,b,c) do {} while(0) +#define sepgsql_relation_lock(a) do {} while(0) +#define sepgsql_view_replace(a) do {} while(0) +#define sepgsql_index_create(a,b,c) do {} while(0) +#define sepgsql_sequence_get_value(a) do {} while(0) +#define sepgsql_sequence_next_value(a) do {} while(0) +#define sepgsql_sequence_set_value(a) do {} while(0) + +#define sepgsql_conversion_create(a,b,c) do {} while(0) +#define sepgsql_conversion_alter(a,b) do {} while(0) +#define sepgsql_conversion_drop(a) do {} while(0) + +#define sepgsql_database_create(a,b) (InvalidOid) +#define sepgsql_database_alter(a) do {} while(0) +#define sepgsql_database_drop(a) do {} while(0) +#define sepgsql_database_relabel(a,b) (InvalidOid) +#define sepgsql_database_grant(a) do {} while(0) +#define sepgsql_database_access(a) do {} while(0) +#define sepgsql_database_superuser(a) (true) +#define sepgsql_database_load_module(a,b) do {} while(0) + +#define sepgsql_fdw_create(a,b) (InvalidOid) +#define sepgsql_fdw_alter(a,b) do {} while(0) +#define sepgsql_fdw_drop(a) do {} while(0) +#define sepgsql_fdw_grant(a) do {} while(0) + +#define sepgsql_foreign_server_create(a) (InvalidOid) +#define sepgsql_foreign_server_alter(a) do {} while(0) +#define sepgsql_foreign_server_drop(a) do {} while(0) +#define sepgsql_foreign_server_grant(a) do {} while(0) + +#define sepgsql_language_create(a,b,c) (InvalidOid) +#define sepgsql_language_alter(a) do {} while(0) +#define sepgsql_language_drop(a) do {} while(0) +#define sepgsql_language_grant(a) do {} while(0) + +#define sepgsql_largeobject_create(a,b) (InvalidOid) +#define sepgsql_largeobject_alter(a,b) do {} while(0) +#define sepgsql_largeobject_drop(a) do {} while(0) +#define sepgsql_largeobject_read(a) do {} while(0) +#define sepgsql_largeobject_write(a) do {} while(0) +#define sepgsql_largeobject_export(a,b) do {} while(0) +#define sepgsql_largeobject_import(a,b) (InvalidOid) + +#define sepgsql_schema_create(a,b,c) (InvalidOid) +#define sepgsql_schema_alter(a) do {} while(0) +#define sepgsql_schema_drop(a) do {} while(0) +#define sepgsql_schema_relabel(a,b) (InvalidOid) +#define sepgsql_schema_grant(a) do {} while(0) +#define sepgsql_schema_search(a,b) (true) + +#define sepgsql_opclass_create(a,b) (InvalidOid) +#define sepgsql_opclass_alter(a,b) do {} while(0) +#define sepgsql_opclass_drop(a) do {} while(0) + +#define sepgsql_opfamily_create(a,b) (InvalidOid) +#define sepgsql_opfamily_alter(a,b) do {} while(0) +#define sepgsql_opfamily_drop(a) do {} while(0) +#define sepgsql_opfamily_add_operator(a,b) do {} while(0) +#define sepgsql_opfamily_add_procedure(a,b) do {} while(0) + +#define sepgsql_operator_create(a,b,c,d,e,f) (InvalidOid) +#define sepgsql_operator_alter(a) do {} while(0) +#define sepgsql_operator_drop(a) do {} while(0) + +#define sepgsql_proc_create(a,b,c,d,e) (InvalidOid) +#define sepgsql_proc_alter(a,b,c) do {} while(0) +#define sepgsql_proc_drop(a) do {} while(0) +#define sepgsql_proc_relabel(a,b) (InvalidOid) +#define sepgsql_proc_grant(a) do {} while(0) +#define sepgsql_proc_execute(a) do {} while(0) +#define sepgsql_proc_hint_inlined(a) (true) +#define sepgsql_proc_entrypoint(a,b) do {} while(0) + +#define sepgsql_rule_create(a,b) do {} while(0) +#define sepgsql_rule_drop(a,b) do {} while(0) + +#define sepgsql_trigger_create(a,b,c) do {} while(0) +#define sepgsql_trigger_alter(a,b) do {} while(0) +#define sepgsql_trigger_drop(a,b) do {} while(0) + +#define sepgsql_ts_config_create(a,b) (InvalidOid) +#define sepgsql_ts_config_alter(a,b) do {} while(0) +#define sepgsql_ts_config_drop(a) do {} while(0) + +#define sepgsql_ts_config_create(a,b) (InvalidOid) +#define sepgsql_ts_config_alter(a,b) do {} while(0) +#define sepgsql_ts_config_drop(a) do {} while(0) + +#define sepgsql_ts_dict_create(a,b) (InvalidOid) +#define sepgsql_ts_dict_alter(a,b) do {} while(0) +#define sepgsql_ts_dict_drop(a) do {} while(0) + +#define sepgsql_ts_parser_create(a,b,c,d,e,f,g) (InvalidOid) +#define sepgsql_ts_parser_alter(a,b) do {} while(0) +#define sepgsql_ts_parser_drop(a) do {} while(0) + +#define sepgsql_ts_template_create(a,b,c,d) (InvalidOid) +#define sepgsql_ts_template_alter(a,b) do {} while(0) +#define sepgsql_ts_template_drop(a) do {} while(0) + +#define sepgsql_type_create(a,b,c,d,e,f,g,h,i,j) (InvalidOid) +#define sepgsql_type_alter(a,b,c) do {} while(0) +#define sepgsql_type_drop(a) do {} while(0) + +#define sepgsql_sysobj_drop(a) do {} while(0) + +#define sepgsql_file_stat(a) do {} while(0) +#define sepgsql_file_read(a) do {} while(0) +#define sepgsql_file_write(a) do {} while(0) + +/* label.c */ +#define sepgsqlTupleDescHasSecLabel(a,b) (false) +#define sepgsqlSetDefaultSecLabel(a,b) do {} while(0) +#define sepgsqlTransSecLabelIn(a) (a) +#define sepgsqlTransSecLabelOut(a) (a) +#define sepgsqlRawSecLabelIn(a) (a) +#define sepgsqlRawSecLabelOut(a) (a) + +#endif /* HAVE_SELINUX */ + +extern Datum sepgsql_getcon(PG_FUNCTION_ARGS); +extern Datum sepgsql_server_getcon(PG_FUNCTION_ARGS); +extern Datum sepgsql_get_user(PG_FUNCTION_ARGS); +extern Datum sepgsql_get_role(PG_FUNCTION_ARGS); +extern Datum sepgsql_get_type(PG_FUNCTION_ARGS); +extern Datum sepgsql_get_range(PG_FUNCTION_ARGS); +extern Datum sepgsql_set_user(PG_FUNCTION_ARGS); +extern Datum sepgsql_set_role(PG_FUNCTION_ARGS); +extern Datum sepgsql_set_type(PG_FUNCTION_ARGS); +extern Datum sepgsql_set_range(PG_FUNCTION_ARGS); + +#endif /* SEPGSQL_H */ diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index 462f6d1..d2c5e6e 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -68,6 +68,7 @@ extern int FileWrite(File file, char *buffer, int amount); extern int FileSync(File file); extern off_t FileSeek(File file, off_t offset, int whence); extern int FileTruncate(File file, off_t offset); +extern int FileRawDescriptor(File file); /* Operations that allow use of regular stdio --- USE WITH CAUTION */ extern FILE *AllocateFile(const char *name, const char *mode); diff --git a/src/include/storage/large_object.h b/src/include/storage/large_object.h index 818db40..feb066f 100644 --- a/src/include/storage/large_object.h +++ b/src/include/storage/large_object.h @@ -70,7 +70,7 @@ typedef struct LargeObjectDesc /* inversion stuff in inv_api.c */ extern void close_lo_relation(bool isCommit); -extern Oid inv_create(Oid lobjId); +extern Oid inv_create(Oid lobjId, Oid secid); extern LargeObjectDesc *inv_open(Oid lobjId, int flags, MemoryContext mcxt); extern void inv_close(LargeObjectDesc *obj_desc); extern int inv_drop(Oid lobjId); diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index e389c61..1ea6c09 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -67,6 +67,7 @@ typedef enum LWLockId AutovacuumLock, AutovacuumScheduleLock, SyncScanLock, + SepgsqlAvcLock, /* Individual lock IDs end here */ FirstBufMappingLock, FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS, diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 48872d9..3383637 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -143,8 +143,10 @@ typedef struct PROC_HDR * normal operation. Startup process also consumes one slot, but WAL * writer and autovacuum launcher are launched only after it has * exited. + * In addition, a netlink receiver process may be launched, if SELinux + * support is enabled. */ -#define NUM_AUXILIARY_PROCS 3 +#define NUM_AUXILIARY_PROCS 4 /* configurable options */ diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h index 44018cd..b225468 100644 --- a/src/include/utils/errcodes.h +++ b/src/include/utils/errcodes.h @@ -301,6 +301,7 @@ #define ERRCODE_INVALID_SCHEMA_DEFINITION MAKE_SQLSTATE('4','2', 'P','1','5') #define ERRCODE_INVALID_TABLE_DEFINITION MAKE_SQLSTATE('4','2', 'P','1','6') #define ERRCODE_INVALID_OBJECT_DEFINITION MAKE_SQLSTATE('4','2', 'P','1','7') +#define ERRCODE_INVALID_SECURITY_LABEL MAKE_SQLSTATE('4','2', 'P','9','9') /* Class 44 - WITH CHECK OPTION Violation */ #define ERRCODE_WITH_CHECK_OPTION_VIOLATION MAKE_SQLSTATE('4','4', '0','0','0') diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile index 8ce5d25..70d7def 100644 --- a/src/test/regress/GNUmakefile +++ b/src/test/regress/GNUmakefile @@ -38,6 +38,12 @@ ifdef NO_LOCALE NOLOCALE += --no-locale endif +# SELinux support +ENABLE_SELINUX = +ifdef SELINUX +ENABLE_SELINUX += --enable-selinux +endif + # stuff to pass into build of pg_regress EXTRADEFS = '-DHOST_TUPLE="$(host_tuple)"' \ '-DMAKEPROG="$(MAKE)"' \ @@ -138,7 +144,7 @@ tablespace-setup: ## Run tests ## -pg_regress_call = ./pg_regress --inputdir=$(srcdir) --dlpath=. --multibyte=$(MULTIBYTE) --load-language=plpgsql $(NOLOCALE) +pg_regress_call = ./pg_regress --inputdir=$(srcdir) --dlpath=. --multibyte=$(MULTIBYTE) --load-language=plpgsql $(NOLOCALE) $(ENABLE_SELINUX) check: all $(pg_regress_call) --temp-install=./tmp_check --top-builddir=$(top_builddir) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) $(TEMP_CONF) diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 9a66ba0..e0efefc 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -113,6 +113,7 @@ SELECT relname, relhasindex pg_pltemplate | t pg_proc | t pg_rewrite | t + pg_security | t pg_shdepend | t pg_shdescription | t pg_statistic | t diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 37193ae..a5f05cc 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -82,6 +82,7 @@ static _stringlist *schedulelist = NULL; static _stringlist *extra_tests = NULL; static char *temp_install = NULL; static char *temp_config = NULL; +static bool enable_selinux = false; static char *top_builddir = NULL; static bool nolocale = false; static char *hostname = NULL; @@ -1863,6 +1864,7 @@ help(void) printf(_(" --top-builddir=DIR (relative) path to top level build directory\n")); printf(_(" --port=PORT start postmaster on PORT\n")); printf(_(" --temp-config=PATH append contents of PATH to temporary config\n")); + printf(_(" --enable-selinux enables SELinux support, if available\n")); printf(_("\n")); printf(_("Options for using an existing installation:\n")); printf(_(" --host=HOST use postmaster running on HOST\n")); @@ -1907,6 +1909,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc {"dlpath", required_argument, NULL, 17}, {"create-role", required_argument, NULL, 18}, {"temp-config", required_argument, NULL, 19}, + {"enable-selinux", optional_argument, NULL, 20}, {NULL, 0, NULL, 0} }; @@ -1997,6 +2000,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc case 19: temp_config = strdup(optarg); break; + case 20: + enable_selinux = true; + break; default: /* getopt_long already emitted a complaint */ fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"), @@ -2086,10 +2092,11 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc /* initdb */ header(_("initializing database system")); snprintf(buf, sizeof(buf), - SYSTEMQUOTE "\"%s/initdb\" -D \"%s/data\" -L \"%s\" --noclean%s%s > \"%s/log/initdb.log\" 2>&1" SYSTEMQUOTE, + SYSTEMQUOTE "\"%s/initdb\" -D \"%s/data\" -L \"%s\" --noclean%s%s%s > \"%s/log/initdb.log\" 2>&1" SYSTEMQUOTE, bindir, temp_install, datadir, debug ? " --debug" : "", nolocale ? " --no-locale" : "", + enable_selinux ? " --enable-selinux" : "", outputdir); if (system(buf)) {