diff -rpNU3 pgace/configure sepgsql/configure --- pgace/configure 2008-03-12 14:39:14.000000000 +0900 +++ sepgsql/configure 2008-03-12 15:47:54.000000000 +0900 @@ -704,6 +704,7 @@ with_libxml with_libxslt with_system_tzdata with_zlib +enable_selinux GREP EGREP ELF_SYS @@ -1362,6 +1363,7 @@ Optional Features: --enable-cassert enable assertion checks (for debugging) --enable-thread-safety make client libraries thread-safe --enable-thread-safety-force force thread-safety despite thread test failure + --enable-selinux build with NSA SELinux support --disable-largefile omit support for large files Optional Packages: @@ -5095,6 +5097,115 @@ fi # +# NSA 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 + { echo "$as_me:$LINENO: checking for getpeercon in -lselinux" >&5 +echo $ECHO_N "checking for getpeercon in -lselinux... $ECHO_C" >&6; } +if test "${ac_cv_lib_selinux_getpeercon+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 getpeercon (); +int +main () +{ +return getpeercon (); + ; + 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_getpeercon=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_cv_lib_selinux_getpeercon=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_getpeercon" >&5 +echo "${ECHO_T}$ac_cv_lib_selinux_getpeercon" >&6; } +if test $ac_cv_lib_selinux_getpeercon = yes; then + cat >>confdefs.h <<\_ACEOF +#define SECURITY_SYSATTR_NAME "security_context" +_ACEOF + + 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 + +fi + +# # Elf # @@ -26063,6 +26174,7 @@ 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 GREP!$GREP$ac_delim EGREP!$EGREP$ac_delim ELF_SYS!$ELF_SYS$ac_delim @@ -26073,7 +26185,6 @@ ld_R_works!$ld_R_works$ac_delim RANLIB!$RANLIB$ac_delim STRIP!$STRIP$ac_delim STRIP_STATIC_LIB!$STRIP_STATIC_LIB$ac_delim -STRIP_SHARED_LIB!$STRIP_SHARED_LIB$ac_delim _ACEOF if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 97; then @@ -26115,6 +26226,7 @@ _ACEOF ac_delim='%!_!# ' for ac_last_try in false false false false false :; do cat >conf$$subs.sed <<_ACEOF +STRIP_SHARED_LIB!$STRIP_SHARED_LIB$ac_delim TAR!$TAR$ac_delim LN_S!$LN_S$ac_delim AWK!$AWK$ac_delim @@ -26165,7 +26277,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` = 48; then + if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 49; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 diff -rpNU3 pgace/configure.in sepgsql/configure.in --- pgace/configure.in 2008-03-12 14:39:14.000000000 +0900 +++ sepgsql/configure.in 2008-03-12 15:47:54.000000000 +0900 @@ -620,6 +620,19 @@ PGAC_ARG_BOOL(with, zlib, yes, AC_SUBST(with_zlib) # +# NSA SELinux support +# +PGAC_ARG_BOOL(enable, selinux, no, + [ --enable-selinux build with NSA SELinux support]) +if test "$enable_selinux" = yes; then + AC_CHECK_LIB(selinux, getpeercon, + AC_DEFINE(SECURITY_SYSATTR_NAME, "security_context") + AC_DEFINE_UNQUOTED(HAVE_SELINUX, 1) + AC_SUBST(enable_selinux), + AC_MSG_ERROR("--enable-selinux requires libselinux.")) +fi + +# # Elf # diff -rpNU3 pgace/src/Makefile.global.in sepgsql/src/Makefile.global.in --- pgace/src/Makefile.global.in 2008-03-12 14:39:14.000000000 +0900 +++ sepgsql/src/Makefile.global.in 2008-03-12 15:47:54.000000000 +0900 @@ -165,6 +165,7 @@ enable_rpath = @enable_rpath@ enable_nls = @enable_nls@ enable_debug = @enable_debug@ enable_dtrace = @enable_dtrace@ +enable_selinux = @enable_selinux@ enable_thread_safety = @enable_thread_safety@ python_includespec = @python_includespec@ diff -rpNU3 pgace/src/backend/Makefile sepgsql/src/backend/Makefile --- pgace/src/backend/Makefile 2008-03-12 14:39:14.000000000 +0900 +++ sepgsql/src/backend/Makefile 2008-03-12 15:47:54.000000000 +0900 @@ -32,6 +32,11 @@ LIBS := $(filter-out -lpgport, $(LIBS)) # The backend doesn't need everything that's in LIBS, however LIBS := $(filter-out -lz -lreadline -ledit -ltermcap -lncurses -lcurses, $(LIBS)) +# SELinux support needs to link libselinux +ifeq ($(enable_selinux), yes) +LIBS += -lselinux +endif + ########################################################################## all: submake-libpgport postgres $(POSTGRES_IMP) diff -rpNU3 pgace/src/backend/security/Makefile sepgsql/src/backend/security/Makefile --- pgace/src/backend/security/Makefile 2008-03-13 13:21:22.000000000 +0900 +++ sepgsql/src/backend/security/Makefile 2008-03-13 14:15:22.000000000 +0900 @@ -10,4 +10,9 @@ include $(top_builddir)/src/Makefile.glo OBJS := pgaceCommon.o pgaceHooks.o +ifeq ($(enable_selinux), yes) +OBJS += sepgsql/core.o sepgsql/hooks.o \ + sepgsql/permissions.o sepgsql/proxy.o +endif + include $(top_builddir)/src/backend/common.mk diff -rpNU3 pgace/src/backend/security/pgaceHooks.c sepgsql/src/backend/security/pgaceHooks.c --- pgace/src/backend/security/pgaceHooks.c 2008-03-13 14:06:46.000000000 +0900 +++ sepgsql/src/backend/security/pgaceHooks.c 2008-03-13 14:15:22.000000000 +0900 @@ -8,6 +8,12 @@ #include "security/pgace.h" +#ifdef HAVE_SELINUX +#include "executor/executor.h" +#include "security/pgace.h" +#include "security/sepgsql.h" +#endif /* HAVE_SELINUX */ + /****************************************************************** * Initialize / Finalize related hooks ******************************************************************/ @@ -19,6 +25,10 @@ */ Size pgaceShmemSize(void) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlShmemSize(); +#endif return (Size) 0; } @@ -30,6 +40,10 @@ Size pgaceShmemSize(void) */ void pgaceInitialize(bool is_bootstrap) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlInitialize(is_bootstrap); +#endif /* do nothing */ } @@ -40,6 +54,10 @@ void pgaceInitialize(bool is_bootstrap) */ bool pgaceInitializePostmaster(void) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlInitializePostmaster(); +#endif return true; } @@ -49,6 +67,10 @@ bool pgaceInitializePostmaster(void) */ void pgaceFinalizePostmaster(void) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlFinalizePostmaster(); +#endif /* do nothing */ } @@ -65,6 +87,19 @@ void pgaceFinalizePostmaster(void) */ List *pgaceProxyQuery(List *queryList) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) { + List *newList = NIL; + ListCell *l; + + foreach (l, queryList) { + Query *q = (Query *) lfirst(l); + + newList = list_concat(newList, sepgsqlProxyQuery(q)); + } + queryList = newList; + } +#endif return queryList; } @@ -87,6 +122,12 @@ void pgacePortalStart(Portal portal) */ void pgaceExecutorStart(QueryDesc *queryDesc, int eflags) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled() && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) { + Assert(queryDesc->plannedstmt != NULL); + sepgsqlVerifyQuery(queryDesc->plannedstmt); + } +#endif /* do nothing */ } @@ -107,6 +148,10 @@ void pgaceExecutorStart(QueryDesc *query bool pgaceHeapTupleInsert(Relation rel, HeapTuple tuple, bool is_internal, bool with_returning) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlHeapTupleInsert(rel, tuple, is_internal, with_returning); +#endif return true; } @@ -124,6 +169,10 @@ bool pgaceHeapTupleInsert(Relation rel, bool pgaceHeapTupleUpdate(Relation rel, ItemPointer otid, HeapTuple newtup, bool is_internal, bool with_returning) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlHeapTupleUpdate(rel, otid, newtup, is_internal, with_returning); +#endif return true; } @@ -140,6 +189,10 @@ bool pgaceHeapTupleUpdate(Relation rel, bool pgaceHeapTupleDelete(Relation rel, ItemPointer otid, bool is_internal, bool with_returning) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlHeapTupleDelete(rel, otid, is_internal, with_returning); +#endif return true; } @@ -157,6 +210,10 @@ bool pgaceHeapTupleDelete(Relation rel, */ DefElem *pgaceGramSecurityItem(char *defname, char *value) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlGramSecurityItem(defname, value); +#endif return NULL; } @@ -168,6 +225,10 @@ DefElem *pgaceGramSecurityItem(char *def */ bool pgaceIsGramSecurityItem(DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlIsGramSecurityItem(defel); +#endif return false; } @@ -181,6 +242,10 @@ bool pgaceIsGramSecurityItem(DefElem *de */ void pgaceGramCreateRelation(Relation rel, HeapTuple tuple, DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlGramCreateRelation(rel, tuple, defel); +#endif /* do nothing */ } @@ -194,6 +259,10 @@ void pgaceGramCreateRelation(Relation re */ void pgaceGramCreateAttribute(Relation rel, HeapTuple tuple, DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlGramCreateAttribute(rel, tuple, defel); +#endif /* do nothing */ } @@ -207,6 +276,10 @@ void pgaceGramCreateAttribute(Relation r */ void pgaceGramAlterRelation(Relation rel, HeapTuple tuple, DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlGramAlterRelation(rel, tuple, defel); +#endif /* do nothing */ } @@ -220,6 +293,10 @@ void pgaceGramAlterRelation(Relation rel */ void pgaceGramAlterAttribute(Relation rel, HeapTuple tuple, DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlGramAlterAttribute(rel, tuple, defel); +#endif /* do nothing */ } @@ -233,6 +310,10 @@ void pgaceGramAlterAttribute(Relation re */ void pgaceGramCreateDatabase(Relation rel, HeapTuple tuple, DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlGramCreateDatabase(rel, tuple, defel); +#endif /* do nothing */ } @@ -246,6 +327,10 @@ void pgaceGramCreateDatabase(Relation re */ void pgaceGramAlterDatabase(Relation rel, HeapTuple tuple, DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlGramAlterDatabase(rel, tuple, defel); +#endif /* do nothing */ } @@ -259,6 +344,10 @@ void pgaceGramAlterDatabase(Relation rel */ void pgaceGramCreateFunction(Relation rel, HeapTuple tuple, DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlGramCreateFunction(rel, tuple, defel); +#endif /* do nothing */ } @@ -272,6 +361,10 @@ void pgaceGramCreateFunction(Relation re */ void pgaceGramAlterFunction(Relation rel, HeapTuple tuple, DefElem *defel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlGramAlterFunction(rel, tuple, defel); +#endif /* do nothing */ } @@ -288,6 +381,10 @@ void pgaceGramAlterFunction(Relation rel */ void pgaceSetDatabaseParam(const char *name, char *argstring) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlSetDatabaseParam(name, argstring); +#endif /* do nothing */ } @@ -298,6 +395,10 @@ void pgaceSetDatabaseParam(const char *n */ void pgaceGetDatabaseParam(const char *name) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlGetDatabaseParam(name); +#endif /* do nothing */ } @@ -313,6 +414,10 @@ void pgaceGetDatabaseParam(const char *n */ void pgaceCallFunction(FmgrInfo *finfo) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlCallFunction(finfo, false); +#endif /* do nothing */ } @@ -328,6 +433,10 @@ void pgaceCallFunction(FmgrInfo *finfo) */ bool pgaceCallFunctionTrigger(FmgrInfo *finfo, TriggerData *tgdata) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlCallFunctionTrigger(finfo, tgdata); +#endif return true; } @@ -339,6 +448,10 @@ bool pgaceCallFunctionTrigger(FmgrInfo * */ void pgaceCallFunctionFastPath(FmgrInfo *finfo) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlCallFunction(finfo, true); +#endif /* do nothing */ } @@ -350,6 +463,14 @@ void pgaceCallFunctionFastPath(FmgrInfo */ Datum pgacePreparePlanCheck(Relation rel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) { + Oid saved; + + saved = sepgsqlPreparePlanCheck(rel); + return ObjectIdGetDatum(saved); + } +#endif return (Datum) 0; } @@ -363,6 +484,10 @@ Datum pgacePreparePlanCheck(Relation rel */ void pgaceRestorePlanCheck(Relation rel, Datum pgace_saved) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlRestorePlanCheck(rel, DatumGetObjectId(pgace_saved)); +#endif /* do nothing */ } @@ -377,6 +502,10 @@ void pgaceRestorePlanCheck(Relation rel, */ void pgaceLockTable(Oid relid) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLockTable(relid); +#endif /* do nothing */ } @@ -392,6 +521,10 @@ void pgaceLockTable(Oid relid) * @isFrom : true, if the given statement is 'COPY FROM' */ void pgaceCopyTable(Relation rel, List *attNumList, bool isFrom) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlCopyTable(rel, attNumList, isFrom); +#endif /* do nothing */ } @@ -405,6 +538,10 @@ void pgaceCopyTable(Relation rel, List * * @tuple : the target tuple */ bool pgaceCopyToTuple(Relation rel, List *attNumList, HeapTuple tuple) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlCopyToTuple(rel, attNumList, tuple); +#endif return true; } @@ -419,6 +556,10 @@ bool pgaceCopyToTuple(Relation rel, List * @filename : full path name of the shared library module */ void pgaceLoadSharedModule(const char *filename) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLoadSharedModule(filename); +#endif /* do nothing */ } @@ -433,7 +574,12 @@ void pgaceLoadSharedModule(const char *f * @tuple : a tuple which is a part of the target largeobject. */ void pgaceLargeObjectGetSecurity(HeapTuple tuple) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectGetSecurity(tuple); +#else elog(ERROR, "PGACE: There is no guest module."); +#endif } /* @@ -443,7 +589,12 @@ void pgaceLargeObjectGetSecurity(HeapTup * @lo_security : new security attribute specified */ void pgaceLargeObjectSetSecurity(HeapTuple tuple, Oid lo_security) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectSetSecurity(tuple, lo_security); +#else elog(ERROR, "PGACE: There is no guest module."); +#endif } /* @@ -453,6 +604,10 @@ void pgaceLargeObjectSetSecurity(HeapTup * @tuple : a new tuple for the new large object */ void pgaceLargeObjectCreate(Relation rel, HeapTuple tuple) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectCreate(rel, tuple); +#endif /* do nothing */ } @@ -464,6 +619,10 @@ void pgaceLargeObjectCreate(Relation rel * @tuple : one of the tuples within the target large object */ void pgaceLargeObjectDrop(Relation rel, HeapTuple tuple) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectDrop(rel, tuple); +#endif /* do nothing */ } @@ -474,6 +633,10 @@ void pgaceLargeObjectDrop(Relation rel, * @tuple : the head tuple within the given large object */ void pgaceLargeObjectRead(Relation rel, HeapTuple tuple) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectRead(rel, tuple); +#endif /* do nothing */ } @@ -485,6 +648,10 @@ void pgaceLargeObjectRead(Relation rel, * @oldtup : the head tuple in older version, if exist */ void pgaceLargeObjectWrite(Relation rel, HeapTuple newtup, HeapTuple oldtup) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectWrite(rel, newtup, oldtup); +#endif /* do nothing */ } @@ -496,6 +663,10 @@ void pgaceLargeObjectWrite(Relation rel, * @headtup : the head tuple to be truncated. NULL means this BLOB will be expanded. */ void pgaceLargeObjectTruncate(Relation rel, Oid loid, HeapTuple headtup) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectTruncate(rel, loid, headtup); +#endif /* do nothing */ } @@ -505,6 +676,10 @@ void pgaceLargeObjectTruncate(Relation r * @fd : file descriptor to be inported */ void pgaceLargeObjectImport(int fd) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectImport(); +#endif /* do nothing */ } @@ -515,6 +690,10 @@ void pgaceLargeObjectImport(int fd) { * @loid : large object to be exported */ void pgaceLargeObjectExport(int fd, Oid loid) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlLargeObjectExport(); +#endif /* do nothing */ } @@ -531,6 +710,10 @@ void pgaceLargeObjectExport(int fd, Oid */ char *pgaceSecurityLabelIn(char *seclabel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + seclabel = sepgsqlSecurityLabelIn(seclabel); +#endif return seclabel; } @@ -543,6 +726,10 @@ char *pgaceSecurityLabelIn(char *seclabe */ char *pgaceSecurityLabelOut(char *seclabel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + seclabel = sepgsqlSecurityLabelOut(seclabel); +#endif return seclabel; } @@ -559,6 +746,10 @@ char *pgaceSecurityLabelOut(char *seclab */ char *pgaceSecurityLabelCheckValid(char *seclabel) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlSecurityLabelCheckValid(seclabel); +#endif return seclabel; } @@ -571,6 +762,10 @@ char *pgaceSecurityLabelCheckValid(char */ char *pgaceSecurityLabelOfLabel(char *new_label) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlSecurityLabelOfLabel(new_label); +#endif return pstrdup("unlabeled"); } @@ -589,6 +784,10 @@ char *pgaceSecurityLabelOfLabel(char *ne */ Node *pgaceCopyObject(Node *orig) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlCopyObject(orig); +#endif return NULL; } @@ -603,6 +802,10 @@ Node *pgaceCopyObject(Node *orig) */ bool pgaceOutObject(StringInfo str, Node *node) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + sepgsqlOutObject(str, node); +#endif return false; } @@ -615,6 +818,10 @@ bool pgaceOutObject(StringInfo str, Node */ void *pgaceReadObject(char *token) { +#ifdef HAVE_SELINUX + if (sepgsqlIsEnabled()) + return sepgsqlReadObject(token); +#endif return NULL; } @@ -626,3 +833,26 @@ void *pgaceReadObject(char *token) * In this section, you can put function stubs when your security * module is not activated. */ +#ifndef HAVE_SELINUX +/* + * SE-PostgreSQL adds three functions. + * When it is disabled, call them causes an error. + */ +Datum sepgsql_getcon(PG_FUNCTION_ARGS) +{ + elog(ERROR, "%s is not implemented", __FUNCTION__); + PG_RETURN_VOID(); +} + +Datum sepgsql_tuple_perms(PG_FUNCTION_ARGS) +{ + elog(ERROR, "%s is not implemented", __FUNCTION__); + PG_RETURN_VOID(); +} + +Datum sepgsql_tuple_perms_abort(PG_FUNCTION_ARGS) +{ + elog(ERROR, "%s is not implemented", __FUNCTION__); + PG_RETURN_VOID(); +} +#endif /* HAVE_SELINUX */ diff -rpNU3 pgace/src/backend/security/sepgsql/core.c sepgsql/src/backend/security/sepgsql/core.c --- pgace/src/backend/security/sepgsql/core.c 1970-01-01 09:00:00.000000000 +0900 +++ sepgsql/src/backend/security/sepgsql/core.c 2008-02-04 17:40:05.000000000 +0900 @@ -0,0 +1,994 @@ +/* + * src/backend/security/sepgsqlCore.c + * SE-PostgreSQL core facilities like userspace AVC, policy state monitoring. + * + * Copyright (c) 2007 KaiGai Kohei + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/tupdesc.h" +#include "access/xact.h" +#include "catalog/pg_database.h" +#include "libpq/libpq-be.h" +#include "libpq/pqsignal.h" +#include "miscadmin.h" +#include "security/pgace.h" +#include "security/sepgsql.h" +#include "storage/lwlock.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static struct { + struct { + char *name; /* name of object class */ + uint16 inum; /* internal identifier number */ + } tclass; + struct { + char *name; /* name of access vector */ + uint32 inum; /* internal identifier number */ + } av_perms[sizeof(access_vector_t) * 8]; +} selinux_catalog[] = { + { + { "db_database", SECCLASS_DB_DATABASE }, + { + { "create", DB_DATABASE__CREATE }, + { "drop", DB_DATABASE__DROP }, + { "getattr", DB_DATABASE__GETATTR }, + { "setattr", DB_DATABASE__SETATTR }, + { "relabelfrom", DB_DATABASE__RELABELFROM }, + { "relabelto", DB_DATABASE__RELABELTO }, + { "access", DB_DATABASE__ACCESS }, + { "install_module", DB_DATABASE__INSTALL_MODULE }, + { "load_module", DB_DATABASE__LOAD_MODULE }, + { "get_param", DB_DATABASE__GET_PARAM }, + { "set_param", DB_DATABASE__SET_PARAM }, + { NULL, 0UL }, + } + }, + { + { "db_table", SECCLASS_DB_TABLE }, + { + { "create", DB_TABLE__CREATE }, + { "drop", DB_TABLE__DROP }, + { "getattr", DB_TABLE__GETATTR }, + { "setattr", DB_TABLE__SETATTR }, + { "relabelfrom", DB_TABLE__RELABELFROM }, + { "relabelto", DB_TABLE__RELABELTO }, + { "use", DB_TABLE__USE }, + { "select", DB_TABLE__SELECT }, + { "update", DB_TABLE__UPDATE }, + { "insert", DB_TABLE__INSERT }, + { "delete", DB_TABLE__DELETE }, + { "lock", DB_TABLE__LOCK }, + { NULL, 0UL }, + } + }, + { + { "db_procedure", SECCLASS_DB_PROCEDURE }, + { + { "create", DB_PROCEDURE__CREATE }, + { "drop", DB_PROCEDURE__DROP }, + { "getattr", DB_PROCEDURE__GETATTR }, + { "setattr", DB_PROCEDURE__SETATTR }, + { "relabelfrom", DB_PROCEDURE__RELABELFROM }, + { "relabelto", DB_PROCEDURE__RELABELTO }, + { "execute", DB_PROCEDURE__EXECUTE }, + { "entrypoint", DB_PROCEDURE__ENTRYPOINT }, + { NULL, 0UL }, + } + }, + { + { "db_column", SECCLASS_DB_COLUMN }, + { + { "create", DB_COLUMN__CREATE }, + { "drop", DB_COLUMN__DROP }, + { "getattr", DB_COLUMN__GETATTR }, + { "setattr", DB_COLUMN__SETATTR }, + { "relabelfrom", DB_COLUMN__RELABELFROM }, + { "relabelto", DB_COLUMN__RELABELTO }, + { "use", DB_COLUMN__USE }, + { "select", DB_COLUMN__SELECT }, + { "update", DB_COLUMN__UPDATE }, + { "insert", DB_COLUMN__INSERT }, + { NULL, 0UL }, + } + }, + { + { "db_tuple", SECCLASS_DB_TUPLE }, + { + { "relabelfrom", DB_TUPLE__RELABELFROM }, + { "relabelto", DB_TUPLE__RELABELTO }, + { "use", DB_TUPLE__USE }, + { "select", DB_TUPLE__SELECT }, + { "update", DB_TUPLE__UPDATE }, + { "insert", DB_TUPLE__INSERT }, + { "delete", DB_TUPLE__DELETE }, + { NULL, 0UL }, + } + }, + { + { "db_blob", SECCLASS_DB_BLOB }, + { + { "create", DB_BLOB__CREATE }, + { "drop", DB_BLOB__DROP }, + { "getattr", DB_BLOB__GETATTR }, + { "setattr", DB_BLOB__SETATTR }, + { "relabelfrom", DB_BLOB__RELABELFROM }, + { "relabelto", DB_BLOB__RELABELTO }, + { "read", DB_BLOB__READ }, + { "write", DB_BLOB__WRITE }, + { "import", DB_BLOB__IMPORT }, + { "export", DB_BLOB__EXPORT }, + { NULL, 0UL }, + } + }, +}; +#define NUM_SELINUX_CATALOG (sizeof(selinux_catalog) / sizeof(selinux_catalog[0])) + +static const char *sepgsql_class_to_string(uint16 tclass) +{ + int i; + + for (i=0; i < NUM_SELINUX_CATALOG; i++) { + if (selinux_catalog[i].tclass.inum == tclass) + return selinux_catalog[i].tclass.name; + } + /* because tclass didn't match with userspace object classes, + * its external representation is always same as internal one */ + return security_class_to_string((security_class_t) tclass); +} + +static const char *sepgsql_av_perm_to_string(uint16 tclass, uint32 perm) +{ + int i, j; + + for (i=0; i < NUM_SELINUX_CATALOG; i++) { + if (selinux_catalog[i].tclass.inum == tclass) { + char *perm_name; + + for (j=0; (perm_name = selinux_catalog[i].av_perms[j].name); j++) { + if (selinux_catalog[i].av_perms[j].inum == perm) + return perm_name; + } + return "unknown"; + } + } + /* because tclass/perm didn't match with userspace object classes, + * its external representation is always same as internal one */ + return security_av_perm_to_string((security_class_t) tclass, (access_vector_t) perm); +} + +/* + * SE-PostgreSQL Internal AVC(Access Vector Cache) implementation. + * + */ +struct avc_datum { + SHMEM_OFFSET next; + + Oid ssid; /* subject context */ + Oid tsid; /* object context */ + uint16 tclass; /* object class */ + + uint32 allowed; + uint32 decided; + uint32 auditallow; + uint32 auditdeny; + + Oid create; /* newly created context */ + bool is_hot; +}; + +#define AVC_DATUM_CACHE_SLOTS 512 +#define AVC_DATUM_CACHE_MAXNODES 800 +static struct { + LWLockId lock; + SHMEM_OFFSET slot[AVC_DATUM_CACHE_SLOTS]; + SHMEM_OFFSET freelist; + int lru_hint; + int enforcing; + struct avc_datum entry[AVC_DATUM_CACHE_MAXNODES]; + + /* dynamic object class/av permission mapping */ + struct { + struct { + uint16 internal; + security_class_t external; + } tclass; + struct { + uint32 internal; + access_vector_t external; + } av_perms[sizeof(access_vector_t) * 8]; + } catalog[NUM_SELINUX_CATALOG]; +} *avc_shmem = NULL; + +Size sepgsqlShmemSize(void) +{ + return sizeof(*avc_shmem); +} + +static void sepgsql_load_class_av_mapping() +{ + extern char *selinux_mnt; + char buffer[PATH_MAX]; + struct stat st_buf; + int i, j, fd, len; + + if (!selinux_mnt) + goto legacy_mapping; + + /* Does '/selinux/class' exist? */ + snprintf(buffer, sizeof(buffer), "%s/class", selinux_mnt); + if (lstat(buffer, &st_buf) || !S_ISDIR(st_buf.st_mode)) + goto legacy_mapping; + + for (i=0; i < NUM_SELINUX_CATALOG; i++) { + /* obtain external object class number */ + snprintf(buffer, sizeof(buffer), "%s/class/%s/index", + selinux_mnt, selinux_catalog[i].tclass.name); + fd = open(buffer, O_RDONLY); + if (fd < 0) + goto legacy_mapping; + + len = read(fd, buffer, sizeof(buffer)); + close(fd); + if (len < 1) + goto legacy_mapping; + buffer[len] = '\0'; + + avc_shmem->catalog[i].tclass.internal + = selinux_catalog[i].tclass.inum; + avc_shmem->catalog[i].tclass.external + = atoi(buffer); + + /* obtain external access vector number */ + for (j=0; selinux_catalog[i].av_perms[j].name; j++) { + snprintf(buffer, sizeof(buffer), "%s/class/%s/perms/%s", + selinux_mnt, + selinux_catalog[i].tclass.name, + selinux_catalog[i].av_perms[j].name); + fd = open(buffer, O_RDONLY); + if (fd < 0) + goto legacy_mapping; + + len = read(fd, buffer, sizeof(buffer)); + close(fd); + if (len < 1) + goto legacy_mapping; + buffer[len] = '\0'; + + avc_shmem->catalog[i].av_perms[j].internal + = selinux_catalog[i].av_perms[j].inum; + avc_shmem->catalog[i].av_perms[j].external + = (0x0001UL << (atoi(buffer) - 1)); + } + } + return; + +legacy_mapping: + for (i=0; i < NUM_SELINUX_CATALOG; i++) { + uint16 tclass = selinux_catalog[i].tclass.inum; + + avc_shmem->catalog[i].tclass.internal = tclass; + avc_shmem->catalog[i].tclass.external = tclass; + + for (j=0; selinux_catalog[i].av_perms[j].name; j++) { + uint32 av_perm = selinux_catalog[i].av_perms[j].inum; + + avc_shmem->catalog[i].av_perms[j].internal = av_perm; + avc_shmem->catalog[i].av_perms[j].external = av_perm; + } + } + return; +} + +static void sepgsql_avc_reset() +{ + int i, enforcing; + + enforcing = security_getenforce(); + Assert(enforcing==0 || enforcing==1); + + LWLockAcquire(avc_shmem->lock, LW_EXCLUSIVE); + + for (i=0; i < AVC_DATUM_CACHE_SLOTS; i++) + avc_shmem->slot[i] = INVALID_OFFSET; + avc_shmem->freelist = INVALID_OFFSET; + for (i=0; i < AVC_DATUM_CACHE_MAXNODES; i++) { + struct avc_datum *avd = avc_shmem->entry + i; + + memset(avd, 0, sizeof(struct avc_datum)); + avd->next = avc_shmem->freelist; + avc_shmem->freelist = MAKE_OFFSET(avd); + } + sepgsql_load_class_av_mapping(); + avc_shmem->enforcing = enforcing; + + LWLockRelease(avc_shmem->lock); +} + +static void sepgsql_avc_init() +{ + bool found_avc; + + avc_shmem = ShmemInitStruct("SELinux userspace AVC", + sepgsqlShmemSize(), &found_avc); + if (!found_avc) { + avc_shmem->lock = LWLockAssign(); + sepgsql_avc_reset(); + } +} + +static uint32 sepgsql_validate_av_perms(security_class_t tclass, access_vector_t perms) +{ + /* we have to hold LW_SHARED lock at least */ + int i, j; + + for (i=0; i < NUM_SELINUX_CATALOG; i++) { + if (avc_shmem->catalog[i].tclass.external == tclass) { + uint32 __perms = 0; + + for (j=0; j < sizeof(access_vector_t) * 8; j++) { + if (avc_shmem->catalog[i].av_perms[j].external & perms) + __perms |= avc_shmem->catalog[i].av_perms[j].internal; + } + return __perms; + } + } + return (uint32) perms; +} + +static void sepgsql_compute_avc_datum(Oid ssid, Oid tsid, uint16 tclass, + struct avc_datum *avd) +{ + security_class_t tclass_external = tclass; + security_context_t scon, tcon, ncon; + struct av_decision x; + Datum tmp; + int i; + + memset(avd, 0, sizeof(struct avc_datum)); + tmp = DirectFunctionCall1(security_label_raw_out, + ObjectIdGetDatum(ssid)); + scon = DatumGetCString(tmp); + tmp = DirectFunctionCall1(security_label_raw_out, + ObjectIdGetDatum(tsid)); + tcon = DatumGetCString(tmp); + + LWLockAcquire(avc_shmem->lock, LW_SHARED); + /* translate internal tclass into external one, to query the kernel */ + for (i=0; i < NUM_SELINUX_CATALOG; i++) { + if (avc_shmem->catalog[i].tclass.internal == tclass) { + tclass_external = avc_shmem->catalog[i].tclass.external; + break; + } + } + + if (security_compute_av_raw(scon, tcon, tclass_external, 0, &x)) + elog(ERROR, "SELinux: could not compute an access vector decision" + " scon='%s' tcon='%s' tclass=%u", scon, tcon, tclass); + if (security_compute_create_raw(scon, tcon, tclass_external, &ncon) != 0) + elog(ERROR, "SELinux: could not compute an implicit security context" + " scon='%s' tcon='%s' tclass=%u", scon, tcon, tclass); + + avd->ssid = ssid; + avd->tsid = tsid; + avd->tclass = tclass; + + avd->allowed = sepgsql_validate_av_perms(tclass_external, x.allowed); + avd->decided = sepgsql_validate_av_perms(tclass_external, x.decided); + avd->auditallow = sepgsql_validate_av_perms(tclass_external, x.auditallow); + avd->auditdeny = sepgsql_validate_av_perms(tclass_external, x.auditdeny); + LWLockRelease(avc_shmem->lock); + + PG_TRY(); + { + tmp = DirectFunctionCall1(security_label_raw_in, + CStringGetDatum(ncon)); + avd->create = DatumGetObjectId(tmp); + } + PG_CATCH(); + { + freecon(ncon); + PG_RE_THROW(); + } + PG_END_TRY(); + + pfree(scon); + pfree(tcon); + freecon(ncon); +} + +static Oid sepgsql_compute_relabel(Oid ssid, Oid tsid, uint16 tclass) +{ + security_context_t scon, tcon, ncon; + Oid nsid; + Datum tmp; + + tmp = DirectFunctionCall1(security_label_raw_out, + ObjectIdGetDatum(ssid)); + scon = DatumGetCString(tmp); + tmp = DirectFunctionCall1(security_label_raw_out, + ObjectIdGetDatum(tsid)); + tcon = DatumGetCString(tmp); + + if (security_compute_relabel_raw(scon, tcon, tclass, &ncon) != 0) + elog(ERROR, "SELinux: could not compute a relabeled security context" + " scon='%s' tcon='%s' tclass=%u", scon, tcon, tclass); + + PG_TRY(); + { + tmp = DirectFunctionCall1(security_label_raw_in, + CStringGetDatum(ncon)); + nsid = DatumGetObjectId(tmp); + } + PG_CATCH(); + { + freecon(ncon); + PG_RE_THROW(); + } + PG_END_TRY(); + + freecon(ncon); + pfree(scon); + pfree(tcon); + + return nsid; +} + +static bool __avc_audit(uint32 perms, struct avc_datum *avd, char *objname, + char *audit_buf, int buflen) +{ + /* we have to hold LW_SHARED lock at least */ + uint32 denied, audited, mask; + char *context; + int ofs = 0; + + denied = perms & ~avd->allowed; + audited = denied ? (denied & avd->auditdeny) : (perms & avd->auditallow); + if (!audited) + return false; + + ofs += snprintf(audit_buf + ofs, buflen - ofs, "%s {", + denied ? "denied" : "granted"); + for (mask=1; mask; mask <<= 1) { + if (audited & mask) { + ofs += snprintf(audit_buf + ofs, buflen - ofs, " %s", + sepgsql_av_perm_to_string(avd->tclass, mask)); + } + } + ofs += snprintf(audit_buf + ofs, buflen - ofs, " }"); + + context = DatumGetCString(DirectFunctionCall1(security_label_out, + ObjectIdGetDatum(avd->ssid))); + ofs += snprintf(audit_buf + ofs, buflen - ofs, " scontext=%s", context); + pfree(context); + + context = DatumGetCString(DirectFunctionCall1(security_label_out, + ObjectIdGetDatum(avd->tsid))); + ofs += snprintf(audit_buf + ofs, buflen - ofs, " tcontext=%s", context); + pfree(context); + + ofs += snprintf(audit_buf + ofs, buflen - ofs, " tclass=%s", + sepgsql_class_to_string(avd->tclass)); + if (objname) + ofs += snprintf(audit_buf + ofs, buflen - ofs, " name=%s", objname); + + return true; +} + +static inline int sepgsql_avc_hash(Oid ssid, Oid tsid, uint16 tclass) +{ + return ((uint32)ssid ^ ((uint32)tsid << 2) ^ tclass) % AVC_DATUM_CACHE_SLOTS; +} + +static struct avc_datum * +sepgsql_avc_lookup(Oid ssid, Oid tsid, uint16 tclass, uint32 perms) +{ + /* we have to hold LW_SHARED lock at least */ + struct avc_datum *avd; + SHMEM_OFFSET curr; + int hashkey = sepgsql_avc_hash(ssid, tsid, tclass); + + for (curr = avc_shmem->slot[hashkey]; + SHM_OFFSET_VALID(curr); + curr = avd->next) { + avd = (void *)MAKE_PTR(curr); + if (avd->ssid==ssid && avd->tsid==tsid && avd->tclass==tclass + && (perms & avd->decided)==perms) + return avd; + } + return NULL; +} + +static void sepgsql_avc_reclaim() { + /* we have to hold LW_EXCLUSIVE lock */ + SHMEM_OFFSET *prev, next; + struct avc_datum *avd; + + while (!SHM_OFFSET_VALID(avc_shmem->freelist)) { + prev = avc_shmem->slot + avc_shmem->lru_hint; + next = *prev; + while (!SHM_OFFSET_VALID(next)) { + avd = (void *)MAKE_PTR(next); + next = avd->next; + if (avd->is_hot) { + avd->is_hot = false; + } else { + *prev = avd->next; + avd->next = avc_shmem->freelist; + avc_shmem->freelist = MAKE_OFFSET(avd); + } + avd = (void *)MAKE_PTR(next); + } + avc_shmem->lru_hint = (avc_shmem->lru_hint + 1) % AVC_DATUM_CACHE_SLOTS; + } +} + +static void sepgsql_avc_insert(struct avc_datum *tmp) +{ + /* we have to hold LW_EXCLUSIVE lock */ + struct avc_datum *avd; + int hashkey; + + avd = sepgsql_avc_lookup(tmp->ssid, tmp->tsid, tmp->tclass, tmp->decided); + if (avd) + return; + + if (!SHM_OFFSET_VALID(avc_shmem->freelist)) + sepgsql_avc_reclaim(); + Assert(SHM_OFFSET_VALID(avc_shmem->freelist)); + + avd = (void *)MAKE_PTR(avc_shmem->freelist); + avc_shmem->freelist = avd->next; + + memcpy(avd, tmp, sizeof(struct avc_datum)); + avd->is_hot = true; + + hashkey = sepgsql_avc_hash(avd->ssid, avd->tsid, avd->tclass); + avd->next = avc_shmem->slot[hashkey]; + avc_shmem->slot[hashkey] = MAKE_OFFSET(avd); + + return; +} + +static bool __avc_permission(Oid ssid, Oid tsid, uint16 tclass, uint32 perms, + char *objname, struct avc_datum *local_avd) +{ + struct avc_datum *avd; + uint32 denied; + bool rc = true; + bool wlock = false; + + LWLockAcquire(avc_shmem->lock, LW_SHARED); +retry: + avd = sepgsql_avc_lookup(ssid, tsid, tclass, perms); + if (!avd) { + LWLockRelease(avc_shmem->lock); + + sepgsql_compute_avc_datum(ssid, tsid, tclass, local_avd); + + LWLockAcquire(avc_shmem->lock, LW_EXCLUSIVE); + wlock = true; + sepgsql_avc_insert(local_avd); + } else { + memcpy(local_avd, avd, sizeof(struct avc_datum)); + } + denied = perms & ~local_avd->allowed; + if (!perms || denied) { + if (avc_shmem->enforcing) { + errno = EACCES; + rc = false; + } else { + if (!wlock) { + /* update avd need LW_EXCLUSIVE lock onto shmem */ + LWLockRelease(avc_shmem->lock); + LWLockAcquire(avc_shmem->lock, LW_EXCLUSIVE); + wlock = true; + goto retry; + } + /* grant permission to avoid flood of access denied log */ + if (!avd) + avd = sepgsql_avc_lookup(ssid, tsid, tclass, perms); + if (avd) + avd->allowed |= denied; + } + } + LWLockRelease(avc_shmem->lock); + + return rc; +} + +void sepgsql_avc_permission(Oid ssid, Oid tsid, uint16 tclass, uint32 perms, char *objname) +{ + struct avc_datum local_avd; + char audit_buf[4096]; + bool rc; + + rc = __avc_permission(ssid, tsid, tclass, perms, objname, &local_avd); + if (__avc_audit(perms, &local_avd, objname, + audit_buf, sizeof(audit_buf))) { + elog(rc ? NOTICE : ERROR, "SELinux: %s", audit_buf); + } else if (rc != true) { + elog(ERROR, "SELinux: security policy violation."); + } +} + +bool sepgsql_avc_permission_noabort(Oid ssid, Oid tsid, uint16 tclass, uint32 perms, char *objname) +{ + struct avc_datum local_avd; + char audit_buf[4096]; + bool rc; + + rc = __avc_permission(ssid, tsid, tclass, perms, objname, &local_avd); + if (__avc_audit(perms, &local_avd, objname, + audit_buf, sizeof(audit_buf))) { + elog(NOTICE, "SELinux: %s", audit_buf); + } + return rc; +} + +Oid sepgsql_avc_createcon(Oid ssid, Oid tsid, uint16 tclass) +{ + struct avc_datum *avd, local_avd; + Oid nsid; + + LWLockAcquire(avc_shmem->lock, LW_SHARED); + avd = sepgsql_avc_lookup(ssid, tsid, tclass, 0); + if (!avd) { + LWLockRelease(avc_shmem->lock); + + sepgsql_compute_avc_datum(ssid, tsid, tclass, &local_avd); + + LWLockAcquire(avc_shmem->lock, LW_EXCLUSIVE); + sepgsql_avc_insert(&local_avd); + nsid = local_avd.create; + } else { + nsid = avd->create; + } + LWLockRelease(avc_shmem->lock); + + return nsid; +} + +Oid sepgsql_avc_relabelcon(Oid ssid, Oid tsid, uint16 tclass) +{ + /* currently no avc support on relabeling */ + return sepgsql_compute_relabel(ssid, tsid, tclass); +} + +/* sepgsql_getcon() -- returns a security context of client */ +Datum +sepgsql_getcon(PG_FUNCTION_ARGS) +{ + PG_RETURN_OID(sepgsqlGetClientContext()); +} + +/* sepgsql_system_getcon() -- obtain the server's context */ +static Oid sepgsql_system_getcon() +{ + security_context_t context; + Oid ssid; + + if (getcon_raw(&context) != 0) + elog(ERROR, "SELinux: could not obtain security context of server process"); + + PG_TRY(); + { + ssid = DatumGetObjectId(DirectFunctionCall1(security_label_raw_in, + CStringGetDatum(context))); + } + PG_CATCH(); + { + freecon(context); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(context); + return ssid; +} + +/* sepgsql_system_getpeercon() -- obtain the client's context */ +static Oid sepgsql_system_getpeercon(int sockfd) +{ + security_context_t context, __context; + Oid ssid; + + if (getpeercon_raw(sockfd, &context)) { + /* we can set finally fallbacked context */ + __context = getenv("SEPGSQL_FALLBACK_CONTEXT"); + if (!__context) + elog(ERROR, "SELinux: could not obtain security context of database client"); + if (security_check_context(__context) || + selinux_trans_to_raw_context(__context, &context)) + elog(ERROR, "SELinux: '%s' is not a valid context", __context); + } + + PG_TRY(); + { + ssid = DatumGetObjectId(DirectFunctionCall1(security_label_raw_in, + CStringGetDatum(context))); + } + PG_CATCH(); + { + freecon(context); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(context); + return ssid; +} + +/* + * SE-PostgreSQL core functions + * + * sepgsqlGetServerContext() -- obtains server's context + * sepgsqlGetClientContext() -- obtains client's context via getpeercon() + * sepgsqlSetClientContext() -- changes client's context for trusted procedure + * sepgsqlInitialize() -- called when initializing 'postgres' includes bootstraping + * sepgsqlInitializePostmaster() -- called when initializing 'postmaster' + * sepgsqlFinalizePostmaster() -- called when finalizing 'postmaster' to kill + * policy state monitoring process. + * sepgsqlMonitoringPolicyState() -- is implementation of policy state monitoring + * process. + * + */ +static Oid sepgsqlServerContext = InvalidOid; +static Oid sepgsqlClientContext = InvalidOid; + +Oid sepgsqlGetServerContext() +{ + return sepgsqlServerContext; +} + +Oid sepgsqlGetClientContext() +{ + return sepgsqlClientContext; +} + +void sepgsqlSetClientContext(Oid new_context) +{ + sepgsqlClientContext = new_context; +} + +Oid sepgsqlGetDatabaseContext() +{ + HeapTuple tuple; + Oid datcon; + + if (IsBootstrapProcessingMode()) { + return sepgsql_avc_createcon(sepgsqlGetClientContext(), + sepgsqlGetServerContext(), + SECCLASS_DB_DATABASE); + } + + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for database %u", MyDatabaseId); + datcon = HeapTupleGetSecurity(tuple); + ReleaseSysCache(tuple); + + return datcon; +} + +char *sepgsqlGetDatabaseName() +{ + Form_pg_database dat_form; + HeapTuple tuple; + char *datname; + + if (IsBootstrapProcessingMode()) + return NULL; + + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for database %u", MyDatabaseId); + dat_form = (Form_pg_database) GETSTRUCT(tuple); + datname = pstrdup(NameStr(dat_form->datname)); + ReleaseSysCache(tuple); + + return datname; +} + +void sepgsqlInitialize(bool is_bootstrap) +{ + sepgsql_avc_init(); + + if (IsBootstrapProcessingMode()) { + sepgsqlServerContext = sepgsql_system_getcon(); + sepgsqlClientContext = sepgsql_system_getcon(); + sepgsql_avc_permission(sepgsqlGetClientContext(), + sepgsqlGetDatabaseContext(), + SECCLASS_DB_DATABASE, + DB_DATABASE__ACCESS, + NULL); + return; + } + + /* obtain security context of server process */ + sepgsqlServerContext = sepgsql_system_getcon(); + + /* obtain security context of client process */ + if (MyProcPort != NULL) { + sepgsqlClientContext = sepgsql_system_getpeercon(MyProcPort->sock); + } else { + sepgsqlClientContext = sepgsql_system_getcon(); + } + + sepgsql_avc_permission(sepgsqlGetClientContext(), + sepgsqlGetDatabaseContext(), + SECCLASS_DB_DATABASE, + DB_DATABASE__ACCESS, + sepgsqlGetDatabaseName()); +} + +/* sepgsqlMonitoringPolicyState() is worker process to monitor + * the status of SELinux policy. When it is changed, light after the worker + * thread receive a notification via netlink socket. The notification is + * delivered into any PostgreSQL instance by reseting shared avc. + */ +static void sepgsqlMonitoringPolicyState_SIGHUP(int signum) +{ + elog(NOTICE, "SELinux: userspace AVC reset"); + sepgsql_avc_reset(); +} + +static int sepgsqlMonitoringPolicyState() +{ + char buffer[2048]; + struct sockaddr_nl addr; + socklen_t addrlen; + struct nlmsghdr *nlh; + int i, rc, nl_sockfd; + + /* close listen port */ + for (i=3; !close(i); i++); + + /* map shared memory segment */ + sepgsql_avc_init(); + + /* setup the signal handler */ + pqinitmask(); + pqsignal(SIGHUP, sepgsqlMonitoringPolicyState_SIGHUP); + pqsignal(SIGINT, SIG_DFL); + pqsignal(SIGQUIT, SIG_DFL); + pqsignal(SIGTERM, SIG_DFL); + pqsignal(SIGUSR1, SIG_DFL); + pqsignal(SIGUSR2, SIG_DFL); + pqsignal(SIGCHLD, SIG_DFL); + PG_SETMASK(&UnBlockSig); + + /* open netlink socket */ + nl_sockfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_SELINUX); + if (nl_sockfd < 0) { + elog(NOTICE, "SELinux: could not open netlink socket"); + return 1; + } + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = SELNL_GRP_AVC; + if (bind(nl_sockfd, (struct sockaddr *)&addr, sizeof(addr))) { + elog(NOTICE, "SELinux: could not bint netlink socket"); + return 1; + } + + /* waiting loop */ + while (true) { + addrlen = sizeof(addr); + rc = recvfrom(nl_sockfd, buffer, sizeof(buffer), 0, + (struct sockaddr *)&addr, &addrlen); + if (rc < 0) { + if (errno == EINTR) + continue; + elog(NOTICE, "SELinux: netlink recvfrom() errno=%d (%s)", + errno, strerror(errno)); + return 1; + } + + if (addrlen != sizeof(addr)) { + elog(NOTICE, "SELinux: netlink address truncated (len=%d)", addrlen); + return 1; + } + + if (addr.nl_pid) { + elog(NOTICE, "SELinux: netlink received spoofed packet from: %u", addr.nl_pid); + continue; + } + + if (rc == 0) { + elog(NOTICE, "SELinux: netlink received EOF on socket"); + return 1; + } + + nlh = (struct nlmsghdr *)buffer; + + if (nlh->nlmsg_flags & MSG_TRUNC + || nlh->nlmsg_len > (unsigned int)rc) { + elog(NOTICE, "SELinux: netlink incomplete netlink message"); + return 1; + } + + switch (nlh->nlmsg_type) { + case NLMSG_ERROR: { + struct nlmsgerr *err = NLMSG_DATA(nlh); + if (err->error == 0) + break; + elog(NOTICE, "SELinux: netlink error message %d", -err->error); + return 1; + } + case SELNL_MSG_SETENFORCE: { + struct selnl_msg_setenforce *msg = NLMSG_DATA(nlh); + elog(NOTICE, "SELinux: netlink received setenforce notice (enforcing=%d)", msg->val); + sepgsql_avc_reset(); + break; + } + case SELNL_MSG_POLICYLOAD: { + struct selnl_msg_policyload *msg = NLMSG_DATA(nlh); + elog(NOTICE, "SELinux: netlink received policyload notice (seqno=%d)", msg->seqno); + sepgsql_avc_reset(); + break; + } + default: + elog(NOTICE, "SELinux: netlink unknown message type (%d)", nlh->nlmsg_type); + return 1; + } + } + return 0; +} + +static pid_t MonitoringPolicyStatePid = -1; + +int sepgsqlInitializePostmaster() +{ + MonitoringPolicyStatePid = fork(); + if (MonitoringPolicyStatePid == 0) { + exit(sepgsqlMonitoringPolicyState()); + } else if (MonitoringPolicyStatePid < 0) { + elog(NOTICE, "SELinux: could not create a policy state monitoring process."); + return false; + } + return true; +} + +void sepgsqlFinalizePostmaster() +{ + int status; + + if (!sepgsqlIsEnabled()) + return; + + if (MonitoringPolicyStatePid > 0) { + if (kill(MonitoringPolicyStatePid, SIGTERM) < 0) { + elog(NOTICE, "SELinux: could not kill(%u, SIGTERM), (%s)", + MonitoringPolicyStatePid, strerror(errno)); + return; + } + waitpid(MonitoringPolicyStatePid, &status, 0); + } +} + +bool sepgsqlIsEnabled() +{ + static int enabled = -1; + + if (enabled < 0) + enabled = is_selinux_enabled(); + + return enabled > 0 ? true : false; +} diff -rpNU3 pgace/src/backend/security/sepgsql/hooks.c sepgsql/src/backend/security/sepgsql/hooks.c --- pgace/src/backend/security/sepgsql/hooks.c 1970-01-01 09:00:00.000000000 +0900 +++ sepgsql/src/backend/security/sepgsql/hooks.c 2008-02-04 17:40:05.000000000 +0900 @@ -0,0 +1,667 @@ +/* + * src/backend/sepgsqlHooks.c + * SE-PostgreSQL hooks + * + * Copyright 2007 KaiGai Kohei + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/skey.h" +#include "catalog/indexing.h" +#include "catalog/pg_database.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_proc.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "security/pgace.h" +#include "security/sepgsql.h" +#include "utils/fmgroids.h" +#include "utils/syscache.h" +#include +#include +#include +#include + +static HeapTuple __getHeapTupleFromItemPointer(Relation rel, ItemPointer tid) +{ + /* obtain an old tuple */ + Buffer buffer; + PageHeader dp; + ItemId lp; + HeapTupleData tuple; + HeapTuple oldtup; + + buffer = ReadBuffer(rel, ItemPointerGetBlockNumber(tid)); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + dp = (PageHeader) BufferGetPage(buffer); + lp = PageGetItemId(dp, ItemPointerGetOffsetNumber(tid)); + + Assert(ItemIdIsUsed(lp)); + + tuple.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp); + tuple.t_len = ItemIdGetLength(lp); + tuple.t_self = *tid; + tuple.t_tableOid = RelationGetRelid(rel); + oldtup = heap_copytuple(&tuple); + + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + ReleaseBuffer(buffer); + + return oldtup; +} + +/******************************************************************************* + * Extended SQL statement hooks + *******************************************************************************/ +DefElem *sepgsqlGramSecurityItem(char *defname, char *value) +{ + DefElem *n = NULL; + if (!strcmp(defname, "context")) + n = makeDefElem(pstrdup(defname), (Node *) makeString(value)); + return n; +} + +bool sepgsqlIsGramSecurityItem(DefElem *defel) +{ + Assert(IsA(defel, DefElem)); + if (defel->defname && !strcmp(defel->defname, "context")) + return true; + return false; +} + +static void __put_gram_context(HeapTuple tuple, DefElem *defel) +{ + if (defel) { + Oid newcon = DirectFunctionCall1(security_label_in, + CStringGetDatum(strVal(defel->arg))); + HeapTupleSetSecurity(tuple, newcon); + } +} + +void sepgsqlGramCreateRelation(Relation rel, HeapTuple tuple, DefElem *defel) +{ + __put_gram_context(tuple, defel); +} + +void sepgsqlGramCreateAttribute(Relation rel, HeapTuple tuple, DefElem *defel) +{ + __put_gram_context(tuple, defel); +} + +void sepgsqlGramAlterRelation(Relation rel, HeapTuple tuple, DefElem *defel) +{ + __put_gram_context(tuple, defel); +} + +void sepgsqlGramAlterAttribute(Relation rel, HeapTuple tuple, DefElem *defel) +{ + __put_gram_context(tuple, defel); +} + +void sepgsqlGramCreateDatabase(Relation rel, HeapTuple tuple, DefElem *defel) +{ + __put_gram_context(tuple, defel); +} + +void sepgsqlGramAlterDatabase(Relation rel, HeapTuple tuple, DefElem *defel) +{ + __put_gram_context(tuple, defel); +} + +void sepgsqlGramCreateFunction(Relation rel, HeapTuple tuple, DefElem *defel) +{ + __put_gram_context(tuple, defel); +} + +void sepgsqlGramAlterFunction(Relation rel, HeapTuple tuple, DefElem *defel) +{ + __put_gram_context(tuple, defel); +} + +/******************************************************************************* + * DATABASE object related hooks + *******************************************************************************/ + +void sepgsqlGetDatabaseParam(const char *name) +{ + HeapTuple tuple; + NameData audit_name; + + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_DATABASE, + DB_DATABASE__GET_PARAM, + sepgsqlGetTupleName(DatabaseRelationId, tuple, &audit_name)); + ReleaseSysCache(tuple); +} + +void sepgsqlSetDatabaseParam(const char *name, char *argstring) +{ + HeapTuple tuple; + NameData audit_name; + + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_DATABASE, + DB_DATABASE__SET_PARAM, + sepgsqlGetTupleName(DatabaseRelationId, tuple, &audit_name)); + ReleaseSysCache(tuple); +} + +/******************************************************************************* + * RELATION(Table)/ATTRIBTUE(column) object related hooks + *******************************************************************************/ +void sepgsqlLockTable(Oid relid) +{ + HeapTuple tuple; + Form_pg_class classForm; + NameData name; + + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for relation %u", relid); + classForm = (Form_pg_class) GETSTRUCT(tuple); + + if (classForm->relkind == RELKIND_RELATION) + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_TABLE, + DB_TABLE__LOCK, + sepgsqlGetTupleName(RelationRelationId, tuple, &name)); + ReleaseSysCache(tuple); +} + +/******************************************************************************* + * PROCEDURE related hooks + *******************************************************************************/ + +static Datum __callTrustedProcedure(PG_FUNCTION_ARGS) +{ + Oid orig_client_con; + Datum retval; + + /* save original security context */ + orig_client_con = sepgsqlGetClientContext(); + /* set exec context */ + sepgsqlSetClientContext(DatumGetObjectId(fcinfo->flinfo->fn_pgace_data)); + PG_TRY(); + { + retval = fcinfo->flinfo->fn_pgace_addr(fcinfo); + } + PG_CATCH(); + { + sepgsqlSetClientContext(orig_client_con); + PG_RE_THROW(); + } + PG_END_TRY(); + sepgsqlSetClientContext(orig_client_con); + + return retval; +} + +void sepgsqlCallFunction(FmgrInfo *finfo, bool with_perm_check) +{ + HeapTuple tuple; + NameData name; + Oid execcon; + uint32 perms = DB_PROCEDURE__EXECUTE; + + tuple = SearchSysCache(PROCOID, + ObjectIdGetDatum(finfo->fn_oid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for procedure %u", finfo->fn_oid); + + /* check trusted procedure */ + execcon = sepgsql_avc_createcon(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_PROCESS); + if (sepgsqlGetClientContext() != execcon) { + finfo->fn_pgace_addr = finfo->fn_addr; + finfo->fn_pgace_data = ObjectIdGetDatum(execcon); + finfo->fn_addr = __callTrustedProcedure; + + perms |= DB_PROCEDURE__ENTRYPOINT; + } + + if (with_perm_check) { + /* check procedure:{execute entrypoint} permission */ + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_PROCEDURE, + perms, + sepgsqlGetTupleName(ProcedureRelationId, tuple, &name)); + } + ReleaseSysCache(tuple); +} + +bool sepgsqlCallFunctionTrigger(FmgrInfo *finfo, TriggerData *tgdata) +{ + Relation rel = tgdata->tg_relation; + HeapTuple newtup = NULL; + HeapTuple oldtup = NULL; + + if (TRIGGER_FIRED_FOR_STATEMENT(tgdata->tg_event)) + return true; /* statement trigger does not contain any tuple */ + if (TRIGGER_FIRED_BY_INSERT(tgdata->tg_event)) { + if (TRIGGER_FIRED_AFTER(tgdata->tg_event)) + newtup = tgdata->tg_trigtuple; + } else if (TRIGGER_FIRED_BY_UPDATE(tgdata->tg_event)) { + oldtup = tgdata->tg_trigtuple; + if (TRIGGER_FIRED_AFTER(tgdata->tg_event) + && HeapTupleGetSecurity(oldtup) != HeapTupleGetSecurity(tgdata->tg_newtuple)) + newtup = tgdata->tg_newtuple; + } else if (TRIGGER_FIRED_BY_DELETE(tgdata->tg_event)) { + if (TRIGGER_FIRED_AFTER(tgdata->tg_event)) + oldtup = tgdata->tg_trigtuple; + } else { + elog(ERROR, "SELinux: unexpected trigger event type (%u)", tgdata->tg_event); + } + if (oldtup && !sepgsqlCheckTuplePerms(rel, oldtup, NULL, SEPGSQL_PERMS_SELECT, false)) + return false; + if (newtup && !sepgsqlCheckTuplePerms(rel, newtup, NULL, SEPGSQL_PERMS_SELECT, false)) + return false; + + sepgsqlCallFunction(finfo, false); + + return true; +} + +/******************************************************************************* + * LOAD shared library module hook + *******************************************************************************/ +void sepgsqlLoadSharedModule(const char *filename) +{ + security_context_t filecon; + Datum filecon_sid; + + if (getfilecon_raw(filename, &filecon) < 1) + elog(ERROR, "SELinux: could not obtain security context of %s", filename); + PG_TRY(); + { + filecon_sid = DirectFunctionCall1(security_label_raw_in, + CStringGetDatum(filecon)); + } + PG_CATCH(); + { + freecon(filecon); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(filecon); + + sepgsql_avc_permission(sepgsqlGetDatabaseContext(), + DatumGetObjectId(filecon_sid), + SECCLASS_DB_DATABASE, + DB_DATABASE__LOAD_MODULE, + (char *) filename); +} + +/******************************************************************************* + * Binary Large Object hooks + *******************************************************************************/ +void sepgsqlLargeObjectGetSecurity(HeapTuple tuple) { + Oid lo_security = HeapTupleGetSecurity(tuple); + NameData name; + + sepgsql_avc_permission(sepgsqlGetClientContext(), + lo_security, + SECCLASS_DB_BLOB, + DB_BLOB__GETATTR, + sepgsqlGetTupleName(LargeObjectRelationId, tuple, &name)); +} + +void sepgsqlLargeObjectSetSecurity(HeapTuple tuple, Oid lo_security) +{ + NameData name; + + /* check db_blob:{setattr relabelfrom} */ + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_BLOB, + DB_BLOB__SETATTR | DB_BLOB__RELABELFROM, + sepgsqlGetTupleName(LargeObjectRelationId, tuple, &name)); + + /* check db_blob:{relabelto} */ + sepgsql_avc_permission(sepgsqlGetClientContext(), + lo_security, + SECCLASS_DB_BLOB, + DB_BLOB__RELABELTO, + sepgsqlGetTupleName(LargeObjectRelationId, tuple, &name)); +} + +void sepgsqlLargeObjectCreate(Relation rel, HeapTuple tuple) +{ + Oid newcon = sepgsqlComputeImplicitContext(rel, tuple); + NameData name; + + sepgsql_avc_permission(sepgsqlGetClientContext(), + newcon, + SECCLASS_DB_BLOB, + DB_BLOB__CREATE, + sepgsqlGetTupleName(LargeObjectRelationId, tuple, &name)); + HeapTupleSetSecurity(tuple, newcon); +} + +void sepgsqlLargeObjectDrop(Relation rel, HeapTuple tuple) +{ + NameData name; + + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_BLOB, + DB_BLOB__DROP, + sepgsqlGetTupleName(LargeObjectRelationId, tuple, &name)); +} + +void sepgsqlLargeObjectRead(Relation rel, HeapTuple tuple) +{ + sepgsqlCheckTuplePerms(rel, tuple, NULL, + SEPGSQL_PERMS_SELECT | SEPGSQL_PERMS_READ, true); +} + +void sepgsqlLargeObjectWrite(Relation rel, HeapTuple newtup, HeapTuple oldtup) +{ + ScanKeyData skey; + SysScanDesc sd; + HeapTuple tuple; + Oid loid; + + /* update existing region */ + if (HeapTupleIsValid(oldtup)) { + HeapTupleSetSecurity(newtup, HeapTupleGetSecurity(oldtup)); + sepgsqlCheckTuplePerms(rel, newtup, NULL, SEPGSQL_PERMS_UPDATE, true); + return; + } + + /* insert a new large object page */ + loid = ((Form_pg_largeobject) GETSTRUCT(newtup))->loid; + ScanKeyInit(&skey, + Anum_pg_largeobject_loid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(loid)); + sd = systable_beginscan(rel, LargeObjectLOidPNIndexId, true, + SnapshotSelf, 1, &skey); + tuple = systable_getnext(sd); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: large object %u does not exist", loid); + HeapTupleSetSecurity(newtup, HeapTupleGetSecurity(tuple)); + sepgsqlCheckTuplePerms(rel, newtup, NULL, SEPGSQL_PERMS_UPDATE, true); + systable_endscan(sd); +} + +void sepgsqlLargeObjectTruncate(Relation rel, Oid loid, HeapTuple headtup) { + ScanKeyData skey; + SysScanDesc sd; + HeapTuple tuple; + + /* simple truncating case */ + if (HeapTupleIsValid(headtup)) { + sepgsqlCheckTuplePerms(rel, headtup, NULL, SEPGSQL_PERMS_UPDATE, true); + return; + } + + /* terminated in a hole */ + ScanKeyInit(&skey, + Anum_pg_largeobject_loid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(loid)); + sd = systable_beginscan(rel, LargeObjectLOidPNIndexId, true, + SnapshotNow, 1, &skey); + tuple = systable_getnext(sd); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: large object %u does not exist", loid); + sepgsqlCheckTuplePerms(rel, tuple, NULL, SEPGSQL_PERMS_UPDATE, true); + systable_endscan(sd); +} + +void sepgsqlLargeObjectImport() +{ + sepgsql_avc_permission(sepgsqlGetClientContext(), + sepgsqlGetServerContext(), + SECCLASS_DB_BLOB, + DB_BLOB__IMPORT, + NULL); +} + +void sepgsqlLargeObjectExport() +{ + sepgsql_avc_permission(sepgsqlGetClientContext(), + sepgsqlGetServerContext(), + SECCLASS_DB_BLOB, + DB_BLOB__EXPORT, + NULL); +} + +/******************************************************************************* + * security_label hooks + *******************************************************************************/ +char *sepgsqlSecurityLabelIn(char *context) { + security_context_t raw_context, canonical_context; + char *result; + int rc; + + rc = selinux_trans_to_raw_context(context, &raw_context); + if (rc) + elog(ERROR, "SELinux: could not translate MLS label"); + + rc = security_canonicalize_context_raw(raw_context, &canonical_context); + freecon(raw_context); + if (rc) + elog(ERROR, "SELinux: could not formalize security context"); + + PG_TRY(); + { + result = pstrdup(canonical_context); + } + PG_CATCH(); + { + freecon(canonical_context); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(canonical_context); + + return result; +} + +char *sepgsqlSecurityLabelOut(char *raw_context) { + security_context_t context; + char *result; + + if (selinux_raw_to_trans_context(raw_context, &context)) + elog(ERROR, "could not translate MLS label"); + PG_TRY(); + { + result = pstrdup(context); + } + PG_CATCH(); + { + freecon(context); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(context); + + return result; +} + +char *sepgsqlSecurityLabelCheckValid(char *context) { + security_context_t unlbl_con; + char *unlbl_result = NULL; + + if (context && !security_check_context_raw(context)) + return context; + + /* context is invalid one */ + if (security_get_initial_context_raw("unlabeled", &unlbl_con)) + elog(ERROR, "SELinux: could not assign an alternative security context"); + PG_TRY(); + { + unlbl_result = pstrdup(unlbl_con); + } + PG_CATCH(); + { + freecon(unlbl_con); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(unlbl_con); + + return unlbl_result; +} + +char *sepgsqlSecurityLabelOfLabel(char *context) { + HeapTuple tuple; + security_context_t scon, tcon, ncon, _ncon; + int rc; + + /* obtain the security context of pg_security */ + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(SecurityRelationId), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for pg_security"); + tcon = DatumGetCString(DirectFunctionCall1(security_label_raw_out, + ObjectIdGetDatum(HeapTupleGetSecurity(tuple)))); + ReleaseSysCache(tuple); + + /* obtain server's context */ + rc = getcon_raw(&scon); + if (rc) + elog(ERROR, "SELinux: could not obtain server's context"); + + /* compute pg_selinux tuple context */ + rc = security_compute_create_raw(scon, tcon, SECCLASS_DB_TUPLE, &ncon); + pfree(tcon); + freecon(scon); + if (rc) + elog(ERROR, "SELinux: could not compute label of pg_security"); + + /* copy tuple's context */ + PG_TRY(); + { + _ncon = pstrdup(ncon); + } + PG_CATCH(); + { + freecon(ncon); + PG_RE_THROW(); + } + PG_END_TRY(); + + freecon(ncon); + + return _ncon; +} + +/****************************************************************** + * HeapTuple modification hooks + ******************************************************************/ +static bool __TrustedRelationForInternal(Relation rel) +{ + if (RelationGetForm(rel)->relkind != RELKIND_RELATION) + return true; + + switch (RelationGetRelid(rel)) { + case LargeObjectRelationId: + case SecurityRelationId: + return true; + break; + } + return false; +} + +bool sepgsqlHeapTupleInsert(Relation rel, HeapTuple tuple, + bool is_internal, bool with_returning) +{ + uint32 perms; + + /* default context for no explicit labeled tuple */ + if (HeapTupleGetSecurity(tuple) == InvalidOid) { + Oid newcon = sepgsqlComputeImplicitContext(rel, tuple); + HeapTupleSetSecurity(tuple, newcon); + } + if (is_internal && __TrustedRelationForInternal(rel)) + return true; + + perms = SEPGSQL_PERMS_INSERT; + if (with_returning) + perms |= SEPGSQL_PERMS_SELECT; + + return sepgsqlCheckTuplePerms(rel, tuple, NULL, perms, is_internal); +} + +bool sepgsqlHeapTupleUpdate(Relation rel, ItemPointer otid, HeapTuple newtup, + bool is_internal, bool with_returning) +{ + HeapTuple oldtup; + uint32 perms; + bool rc = true; + + oldtup = __getHeapTupleFromItemPointer(rel, otid); + + if (HeapTupleGetSecurity(newtup) == InvalidOid) { + /* keep old context for no explicit labeled tuple */ + HeapTupleSetSecurity(newtup, HeapTupleGetSecurity(oldtup)); + } + + if (is_internal && __TrustedRelationForInternal(rel)) + goto out; + + if (is_internal) { + perms = SEPGSQL_PERMS_UPDATE; + if (HeapTupleGetSecurity(newtup) != HeapTupleGetSecurity(oldtup)) + perms |= SEPGSQL_PERMS_RELABELFROM; + rc = sepgsqlCheckTuplePerms(rel, oldtup, NULL, perms, is_internal); + if (!rc) + goto out; + } + + if (HeapTupleGetSecurity(newtup) != HeapTupleGetSecurity(oldtup)) { + perms = SEPGSQL_PERMS_RELABELTO; + if (with_returning) + perms |= SEPGSQL_PERMS_SELECT; + rc = sepgsqlCheckTuplePerms(rel, newtup, oldtup, perms, is_internal); + } +out: + heap_freetuple(oldtup); + return rc; +} + +bool sepgsqlHeapTupleDelete(Relation rel, ItemPointer otid, + bool is_internal, bool with_returning) +{ + HeapTuple oldtup; + uint32 perms; + bool rc = true; + + if (is_internal) { + if (__TrustedRelationForInternal(rel)) + return true; + + oldtup = __getHeapTupleFromItemPointer(rel, otid); + perms = SEPGSQL_PERMS_DELETE; + if (with_returning) + perms |= SEPGSQL_PERMS_SELECT; + rc = sepgsqlCheckTuplePerms(rel, oldtup, NULL, perms, is_internal); + heap_freetuple(oldtup); + } + return rc; +} diff -rpNU3 pgace/src/backend/security/sepgsql/permissions.c sepgsql/src/backend/security/sepgsql/permissions.c --- pgace/src/backend/security/sepgsql/permissions.c 1970-01-01 09:00:00.000000000 +0900 +++ sepgsql/src/backend/security/sepgsql/permissions.c 2008-02-04 17:40:05.000000000 +0900 @@ -0,0 +1,587 @@ +/* + * src/backend/security/sepgsqlPerms.c + * SE-PostgreSQL permission checking functions + * + * Copyright (c) 2007 KaiGai Kohei + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_class.h" +#include "catalog/pg_database.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_security.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "security/pgace.h" +#include "security/sepgsql.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +/* + * If we have to refere a object which is newly inserted or updated + * in the same command, SearchSysCache() returns NULL because it use + * SnapshowNow internally. The followings are fallback routine to + * avoid a failed cache lookup. + */ +static Oid __lookupRelationForm(Oid relid, Form_pg_class classForm) { + Relation rel; + SysScanDesc scan; + ScanKeyData skey; + HeapTuple tuple; + Oid t_security; + + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (HeapTupleIsValid(tuple)) { + if (classForm) + memcpy(classForm, GETSTRUCT(tuple), sizeof(FormData_pg_class)); + t_security = HeapTupleGetSecurity(tuple); + ReleaseSysCache(tuple); + return t_security; + } + + rel = heap_open(RelationRelationId, AccessShareLock); + ScanKeyInit(&skey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + scan = systable_beginscan(rel, ClassOidIndexId, + true, SnapshotSelf, 1, &skey); + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for relation %u", relid); + + if (classForm) + memcpy(classForm, GETSTRUCT(tuple), sizeof(FormData_pg_class)); + t_security = HeapTupleGetSecurity(tuple); + + systable_endscan(scan); + heap_close(rel, AccessShareLock); + + return t_security; +} + +static uint32 __sepgsql_perms_to_common_perms(uint32 perms) { + uint32 __perms = 0; + + Assert((perms & ~SEPGSQL_PERMS_ALL) == 0); + __perms |= (perms & SEPGSQL_PERMS_USE ? COMMON_DATABASE__GETATTR : 0); + __perms |= (perms & SEPGSQL_PERMS_SELECT ? COMMON_DATABASE__GETATTR : 0); + __perms |= (perms & SEPGSQL_PERMS_UPDATE ? COMMON_DATABASE__SETATTR : 0); + __perms |= (perms & SEPGSQL_PERMS_INSERT ? COMMON_DATABASE__CREATE : 0); + __perms |= (perms & SEPGSQL_PERMS_DELETE ? COMMON_DATABASE__DROP : 0); + __perms |= (perms & SEPGSQL_PERMS_RELABELFROM ? COMMON_DATABASE__RELABELFROM : 0); + __perms |= (perms & SEPGSQL_PERMS_RELABELTO ? COMMON_DATABASE__RELABELTO : 0); + + return __perms; +} + +static uint32 __sepgsql_perms_to_tuple_perms(uint32 perms) { + uint32 __perms = 0; + + Assert((perms & ~SEPGSQL_PERMS_ALL) == 0); + __perms |= (perms & SEPGSQL_PERMS_USE ? DB_TUPLE__USE : 0); + __perms |= (perms & SEPGSQL_PERMS_SELECT ? DB_TUPLE__SELECT : 0); + __perms |= (perms & SEPGSQL_PERMS_UPDATE ? DB_TUPLE__UPDATE : 0); + __perms |= (perms & SEPGSQL_PERMS_INSERT ? DB_TUPLE__INSERT : 0); + __perms |= (perms & SEPGSQL_PERMS_DELETE ? DB_TUPLE__DELETE : 0); + __perms |= (perms & SEPGSQL_PERMS_RELABELFROM ? DB_TUPLE__RELABELFROM : 0); + __perms |= (perms & SEPGSQL_PERMS_RELABELTO ? DB_TUPLE__RELABELTO : 0); + + return __perms; +} + +char *sepgsqlGetTupleName(Oid relid, HeapTuple tuple, NameData *name) +{ + switch (relid) { + case AttributeRelationId: { + Form_pg_attribute attr = (Form_pg_attribute) GETSTRUCT(tuple); + HeapTuple reltup; + + if (IsBootstrapProcessingMode()) { + strncpy(NameStr(*name), + NameStr(attr->attname), + NAMEDATALEN); + return NameStr(*name); + } + reltup = SearchSysCache(RELOID, + ObjectIdGetDatum(attr->attrelid), + 0, 0, 0); + if (!HeapTupleIsValid(reltup)) { + strncpy(NameStr(*name), + NameStr(attr->attname), + NAMEDATALEN); + return NameStr(*name); + } + snprintf(NameStr(*name), NAMEDATALEN, "%s.%s", + NameStr(((Form_pg_class) GETSTRUCT(reltup))->relname), + NameStr(attr->attname)); + ReleaseSysCache(reltup); + return NameStr(*name); + } + case AuthIdRelationId: { + strncpy(NameStr(*name), + NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname), + NAMEDATALEN); + return NameStr(*name); + } + case RelationRelationId: { + strncpy(NameStr(*name), + NameStr(((Form_pg_class) GETSTRUCT(tuple))->relname), + NAMEDATALEN); + return NameStr(*name); + } + case DatabaseRelationId: { + strncpy(NameStr(*name), + NameStr(((Form_pg_database) GETSTRUCT(tuple))->datname), + NAMEDATALEN); + return NameStr(*name); + } + case LargeObjectRelationId: { + snprintf(NameStr(*name), NAMEDATALEN, "loid:%u", + ((Form_pg_largeobject) GETSTRUCT(tuple))->loid); + return NameStr(*name); + } + case ProcedureRelationId: { + strncpy(NameStr(*name), + NameStr(((Form_pg_proc) GETSTRUCT(tuple))->proname), + NAMEDATALEN); + return NameStr(*name); + } + case TriggerRelationId: { + strncpy(NameStr(*name), + NameStr(((Form_pg_trigger) GETSTRUCT(tuple))->tgname), + NAMEDATALEN); + return NameStr(*name); + } + case TypeRelationId: { + snprintf(NameStr(*name), NAMEDATALEN, "pg_type::%s", + NameStr(((Form_pg_type) GETSTRUCT(tuple))->typname)); + return NameStr(*name); + } + default: + if (HeapTupleGetOid(tuple) != InvalidOid) { + snprintf(NameStr(*name), NAMEDATALEN, "relid:%u,oid:%u", + relid, HeapTupleGetOid(tuple)); + return NameStr(*name); + } + break; + } + return NULL; +} + +static void __check_pg_attribute(HeapTuple tuple, HeapTuple oldtup, + uint32 *p_perms, uint16 *p_tclass) +{ + Form_pg_attribute attrForm = (Form_pg_attribute) GETSTRUCT(tuple); + FormData_pg_class classForm; + + switch (attrForm->attrelid) { + case TypeRelationId: + case ProcedureRelationId: + case AttributeRelationId: + case RelationRelationId: + /* those are pure relation */ + break; + default: + __lookupRelationForm(attrForm->attrelid, &classForm); + if (classForm.relkind != RELKIND_RELATION) { + *p_tclass = SECCLASS_DB_TUPLE; + *p_perms = __sepgsql_perms_to_tuple_perms(*p_perms); + return; + } + break; + } + *p_tclass = SECCLASS_DB_COLUMN; + *p_perms = __sepgsql_perms_to_common_perms(*p_perms); + if (HeapTupleIsValid(oldtup)) { + Form_pg_attribute oldForm = (Form_pg_attribute) GETSTRUCT(oldtup); + + if (oldForm->attisdropped != true && attrForm->attisdropped == true) + *p_perms |= DB_COLUMN__DROP; + } +} + +static void __check_pg_largeobject(HeapTuple tuple, HeapTuple oldtup, + uint32 *p_perms, uint16 *p_tclass) +{ + Form_pg_largeobject loForm = (Form_pg_largeobject) GETSTRUCT(tuple); + Relation rel; + ScanKeyData skey; + SysScanDesc sd; + HeapTuple exttup; + uint32 perms = 0; + + perms |= (*p_perms & SEPGSQL_PERMS_USE ? DB_BLOB__GETATTR : 0); + perms |= (*p_perms & SEPGSQL_PERMS_SELECT ? DB_BLOB__GETATTR : 0); + perms |= (*p_perms & SEPGSQL_PERMS_UPDATE ? DB_BLOB__SETATTR | DB_BLOB__WRITE : 0); + perms |= (*p_perms & SEPGSQL_PERMS_RELABELFROM ? DB_BLOB__RELABELFROM : 0); + perms |= (*p_perms & SEPGSQL_PERMS_READ ? DB_BLOB__READ : 0); + perms |= (*p_perms & SEPGSQL_PERMS_WRITE ? DB_BLOB__WRITE : 0); + + if (*p_perms & SEPGSQL_PERMS_INSERT) { + perms |= DB_BLOB__SETATTR | DB_BLOB__WRITE; + ScanKeyInit(&skey, + Anum_pg_largeobject_loid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(loForm->loid)); + rel = heap_open(LargeObjectRelationId, AccessShareLock); + sd = systable_beginscan(rel, LargeObjectLOidPNIndexId, true, + SnapshotSelf, 1, &skey); + /* INSERT the first one means create a largeobject */ + exttup = systable_getnext(sd); + if (!HeapTupleIsValid(exttup)) { + perms |= DB_BLOB__CREATE; + } else if (HeapTupleGetSecurity(tuple) != HeapTupleGetSecurity(exttup)) { + elog(ERROR, "SELinux: inconsistent security context specified"); + } + systable_endscan(sd); + heap_close(rel, AccessShareLock); + } + + if (*p_perms & SEPGSQL_PERMS_DELETE) { + bool found = false; + + perms |= DB_BLOB__SETATTR | DB_BLOB__WRITE; + ScanKeyInit(&skey, + Anum_pg_largeobject_loid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(loForm->loid)); + rel = heap_open(LargeObjectRelationId, AccessShareLock); + sd = systable_beginscan(rel, LargeObjectLOidPNIndexId, true, + SnapshotSelf, 1, &skey); + while ((exttup = systable_getnext(sd))) { + int __pageno = ((Form_pg_largeobject) GETSTRUCT(exttup))->pageno; + + if (loForm->pageno != __pageno) { + found = true; + break; + } + } + systable_endscan(sd); + heap_close(rel, AccessShareLock); + + /* + * If this tuple is the last one with given large object, + * it means to drop the whole of large object. + */ + if (!found) + perms |= DB_BLOB__DROP; + } + + /* + * SE-PostgreSQL does not allow different security contexts are + * held in a single large object. + */ + if (*p_perms & SEPGSQL_PERMS_RELABELTO) { + bool found = false; + + perms |= DB_BLOB__RELABELTO; + ScanKeyInit(&skey, + Anum_pg_largeobject_loid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(loForm->loid)); + rel = heap_open(LargeObjectRelationId, AccessShareLock); + sd = systable_beginscan(rel, LargeObjectLOidPNIndexId, true, + SnapshotSelf, 1, &skey); + while ((exttup = systable_getnext(sd))) { + int __pageno = ((Form_pg_largeobject) GETSTRUCT(exttup))->pageno; + + if (loForm->pageno != __pageno) { + found = true; + break; + } + } + systable_endscan(sd); + heap_close(rel, AccessShareLock); + + if (found) + elog(ERROR, + "SELinux: It's not possible a part of tuples within" + " a single large object to have different security context." + " You can use lo_set_security() instead."); + } + *p_tclass = SECCLASS_DB_BLOB; + *p_perms = perms; +} + +static void __check_pg_proc(HeapTuple tuple, HeapTuple oldtup, + uint32 *p_perms, uint16 *p_tclass) +{ + uint32 perms = __sepgsql_perms_to_common_perms(*p_perms); + Form_pg_proc procForm = (Form_pg_proc) GETSTRUCT(tuple); + + if (procForm->prolang == ClanguageId) { + Datum oldbin, newbin; + bool isnull, verify = false; + + newbin = SysCacheGetAttr(PROCOID, tuple, + Anum_pg_proc_probin, &isnull); + if (!isnull) { + if (perms & DB_PROCEDURE__CREATE) { + verify = true; + } else if (HeapTupleIsValid(oldtup)) { + oldbin = SysCacheGetAttr(PROCOID, oldtup, + Anum_pg_proc_probin, &isnull); + if (isnull || DatumGetBool(DirectFunctionCall2(textne, oldbin, newbin))) + verify = true; + } + + if (verify) { + char *filename; + security_context_t filecon; + Datum filesid; + + /* <-- database:module_install --> */ + sepgsql_avc_permission(sepgsqlGetClientContext(), + sepgsqlGetDatabaseContext(), + SECCLASS_DB_DATABASE, + DB_DATABASE__INSTALL_MODULE, + NULL); + + /* <-- database:module_install --> */ + filename = DatumGetCString(DirectFunctionCall1(textout, newbin)); + filename = expand_dynamic_library_name(filename); + if (getfilecon_raw(filename, &filecon) < 1) + elog(ERROR, "could not obtain the security context of '%s'", filename); + PG_TRY(); + { + filesid = DirectFunctionCall1(security_label_raw_in, + CStringGetDatum(filecon)); + } + PG_CATCH(); + { + freecon(filecon); + PG_RE_THROW(); + } + PG_END_TRY(); + freecon(filecon); + + sepgsql_avc_permission(sepgsqlGetClientContext(), + DatumGetObjectId(filesid), + SECCLASS_DB_DATABASE, + DB_DATABASE__INSTALL_MODULE, + filename); + } + } + } + *p_perms = perms; + *p_tclass = SECCLASS_DB_PROCEDURE; +} + +static void __check_pg_relation(HeapTuple tuple, HeapTuple oldtup, + uint32 *p_perms, uint16 *p_tclass) +{ + Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); + if (classForm->relkind == RELKIND_RELATION) { + *p_tclass = SECCLASS_DB_TABLE; + *p_perms = __sepgsql_perms_to_common_perms(*p_perms); + } else { + *p_tclass = SECCLASS_DB_TUPLE; + *p_perms = __sepgsql_perms_to_tuple_perms(*p_perms); + } +} + +static bool __check_tuple_perms(Oid tableoid, Oid tcontext, uint32 perms, + HeapTuple tuple, HeapTuple oldtup, bool abort) +{ + uint16 tclass; + bool rc = true; + + Assert(tuple != NULL); + + switch (tableoid) { + case DatabaseRelationId: /* pg_database */ + perms = __sepgsql_perms_to_common_perms(perms); + tclass = SECCLASS_DB_DATABASE; + break; + + case RelationRelationId: /* pg_class */ + __check_pg_relation(tuple, oldtup, &perms, &tclass); + break; + + case AttributeRelationId: /* pg_attribute */ + __check_pg_attribute(tuple, oldtup, &perms, &tclass); + break; + + case ProcedureRelationId: /* pg_proc */ + __check_pg_proc(tuple, oldtup, &perms, &tclass); + break; + + case LargeObjectRelationId: /* pg_largeobject */ + __check_pg_largeobject(tuple, oldtup, &perms, &tclass); + break; + + default: + perms = __sepgsql_perms_to_tuple_perms(perms); + tclass = SECCLASS_DB_TUPLE; + break; + } + + if (perms) { + NameData name; + + if (abort) { + sepgsql_avc_permission(sepgsqlGetClientContext(), + tcontext, + tclass, + perms, + sepgsqlGetTupleName(tableoid, tuple, &name)); + } else { + rc = sepgsql_avc_permission_noabort(sepgsqlGetClientContext(), + tcontext, + tclass, + perms, + sepgsqlGetTupleName(tableoid, tuple, &name)); + } + } + return rc; +} + +/* + * MEMO: we cannot obtain system column from RECORD datatype. + * If those are necesasry, they should be separately delivered. + */ +Datum sepgsql_tuple_perms(PG_FUNCTION_ARGS) +{ + Oid tableoid = PG_GETARG_OID(0); + Oid tcontext = PG_GETARG_OID(1); + uint32 perms = PG_GETARG_UINT32(2); + HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(3); + HeapTupleData tuple; + + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + ItemPointerSetInvalid(&tuple.t_self); + tuple.t_tableOid = tableoid; + tuple.t_data = rec; + + PG_RETURN_BOOL(__check_tuple_perms(tableoid, tcontext, perms, &tuple, NULL, false)); +} + +Datum sepgsql_tuple_perms_abort(PG_FUNCTION_ARGS) +{ + Oid tableoid = PG_GETARG_OID(0); + Oid tcontext = PG_GETARG_OID(1); + uint32 perms = PG_GETARG_UINT32(2); + HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(3); + HeapTupleData tuple; + + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + ItemPointerSetInvalid(&tuple.t_self); + tuple.t_tableOid = tableoid; + tuple.t_data = rec; + + PG_RETURN_BOOL(__check_tuple_perms(tableoid, tcontext, perms, &tuple, NULL, true)); +} + +bool sepgsqlCheckTuplePerms(Relation rel, HeapTuple tuple, HeapTuple oldtup, uint32 perms, bool abort) +{ + return __check_tuple_perms(RelationGetRelid(rel), + HeapTupleGetSecurity(tuple), + perms, + tuple, + oldtup, + abort); +} + +Oid sepgsqlComputeImplicitContext(Relation rel, HeapTuple tuple) { + uint16 tclass; + Oid tcon; + + switch (RelationGetRelid(rel)) { + case DatabaseRelationId: /* pg_database */ + tclass = SECCLASS_DB_DATABASE; + tcon = sepgsqlGetServerContext(); + break; + + case RelationRelationId: { /* pg_class */ + Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); + if (classForm->relkind == RELKIND_RELATION) { + tclass = SECCLASS_DB_TABLE; + tcon = sepgsqlGetDatabaseContext(); + break; + } + tcon = __lookupRelationForm(RelationRelationId, NULL); + tclass = SECCLASS_DB_TUPLE; + break; + } + case AttributeRelationId: { /* pg_attribute */ + Form_pg_attribute attrForm = (Form_pg_attribute) GETSTRUCT(tuple); + FormData_pg_class classForm; + + /* special case in bootstraping mode */ + if (IsBootstrapProcessingMode() + && (attrForm->attrelid == TypeRelationId || + attrForm->attrelid == ProcedureRelationId || + attrForm->attrelid == AttributeRelationId || + attrForm->attrelid == RelationRelationId)) { + tcon = sepgsql_avc_createcon(sepgsqlGetClientContext(), + sepgsqlGetDatabaseContext(), + SECCLASS_DB_TABLE); + tclass = SECCLASS_DB_COLUMN; + break; + } + tcon = __lookupRelationForm(attrForm->attrelid, &classForm); + tclass = (classForm.relkind == RELKIND_RELATION + ? SECCLASS_DB_COLUMN + : SECCLASS_DB_TUPLE); + break; + } + case ProcedureRelationId: + tclass = SECCLASS_DB_PROCEDURE; + tcon = sepgsqlGetDatabaseContext(); + break; + + case LargeObjectRelationId: { /* pg_largeobject */ + ScanKeyData skey; + SysScanDesc sd; + HeapTuple lotup; + Oid loid, lo_security = InvalidOid; + + loid = ((Form_pg_largeobject) GETSTRUCT(tuple))->loid; + ScanKeyInit(&skey, + Anum_pg_largeobject_loid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(loid)); + sd = systable_beginscan(rel, LargeObjectLOidPNIndexId, true, + SnapshotSelf, 1, &skey); + lotup = systable_getnext(sd); + if (HeapTupleIsValid(lotup)) + lo_security = HeapTupleGetSecurity(lotup); + systable_endscan(sd); + /* Inherit previous page's security context */ + if (lo_security != InvalidOid) + return lo_security; + /* compute newly created one */ + tclass = SECCLASS_DB_BLOB; + tcon = sepgsqlGetDatabaseContext(); + break; + } + case TypeRelationId: /* pg_type */ + if (IsBootstrapProcessingMode()) { + /* special case in early phase */ + tcon = sepgsql_avc_createcon(sepgsqlGetClientContext(), + sepgsqlGetDatabaseContext(), + SECCLASS_DB_TABLE); + tclass = SECCLASS_DB_TUPLE; + break; + } + default: + tclass = SECCLASS_DB_TUPLE; + tcon = __lookupRelationForm(RelationGetRelid(rel), NULL); + break; + } + return sepgsql_avc_createcon(sepgsqlGetClientContext(), tcon, tclass); +} diff -rpNU3 pgace/src/backend/security/sepgsql/proxy.c sepgsql/src/backend/security/sepgsql/proxy.c --- pgace/src/backend/security/sepgsql/proxy.c 1970-01-01 09:00:00.000000000 +0900 +++ sepgsql/src/backend/security/sepgsql/proxy.c 2008-03-11 16:03:12.000000000 +0900 @@ -0,0 +1,1618 @@ +/* + * src/backend/security/sepgsqlProxy.c + * SE-PostgreSQL Query Proxy function to walk on query node tree + * and append tuple filter. + * + * Copyright KaiGai Kohei + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/heap.h" +#include "catalog/indexing.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_class.h" +#include "catalog/pg_database.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_type.h" +#include "executor/spi.h" +#include "nodes/makefuncs.h" +#include "nodes/readfuncs.h" +#include "optimizer/plancat.h" +#include "parser/parse_relation.h" +#include "parser/parse_target.h" +#include "parser/parsetree.h" +#include "security/pgace.h" +#include "security/sepgsql.h" +#include "storage/lock.h" +#include "utils/fmgroids.h" +#include "utils/syscache.h" + +/* SE-PostgreSQL Evaluation Item */ +#define T_SEvalItem (T_TIDBitmap + 1) /* must be unique identifier */ + +typedef struct SEvalItem { + NodeTag type; + uint16 tclass; + uint32 perms; + union { + struct { + Oid relid; + bool inh; + } c; /* for pg_class */ + struct { + Oid relid; + bool inh; + AttrNumber attno; + } a; /* for pg_attribute */ + struct { + Oid funcid; + } p; /* for pg_proc */ + }; +} SEvalItem; + +/* query stack definition for outer references */ +typedef struct queryChain { + struct queryChain *parent; + Query *tail; +} queryChain; + +static inline queryChain *upperQueryChain(queryChain *qc, int lvup) { + while (lvup > 0) { + Assert(!!qc->parent); + qc = qc->parent; + lvup--; + } + return qc; +} + +static inline Query *getQueryFromChain(queryChain *qc) { + return qc->tail; +} + +/* static definitions for proxy functions */ +static List *proxyRteRelation(List *selist, queryChain *qc, int rtindex, Node **quals); +static List *proxyRteSubQuery(List *selist, queryChain *qc, Query *query); +static List *proxyJoinTree(List *selist, queryChain *qc, Node *n, Node **quals); +static List *proxySetOperations(List *selist, queryChain *qc, Node *n); + +/* static */ +static List *sepgsqlWalkExpr(List *selist, queryChain *qc, Node *n, int flags); +#define WKFLAG_INTERNAL_USE (0x0001) + +/* ----------------------------------------------------------- + * addEvalXXXX -- add evaluation items into Query->SEvalItemList. + * Those are used for execution phase. + * ----------------------------------------------------------- */ +static List *__addEvalPgClass(List *selist, Oid relid, bool inh, uint32 perms) +{ + SEvalItem *se; + ListCell *l; + + foreach (l, selist) { + se = (SEvalItem *) lfirst(l); + if (se->tclass == SECCLASS_DB_TABLE + && se->c.relid == relid + && se->c.inh == inh) { + se->perms |= perms; + return selist; + } + } + /* not found */ + se = makeNode(SEvalItem); + se->tclass = SECCLASS_DB_TABLE; + se->perms = perms; + se->c.relid = relid; + se->c.inh = inh; + return lappend(selist, se); +} + +static List *addEvalPgClass(List *selist, RangeTblEntry *rte, uint32 perms) +{ + rte->requiredPerms |= (perms & DB_TABLE__USE ? SEPGSQL_PERMS_USE : 0); + rte->requiredPerms |= (perms & DB_TABLE__SELECT ? SEPGSQL_PERMS_SELECT : 0); + rte->requiredPerms |= (perms & DB_TABLE__INSERT ? SEPGSQL_PERMS_INSERT : 0); + rte->requiredPerms |= (perms & DB_TABLE__UPDATE ? SEPGSQL_PERMS_UPDATE : 0); + rte->requiredPerms |= (perms & DB_TABLE__DELETE ? SEPGSQL_PERMS_DELETE : 0); + + /* for 'pg_largeobject' */ + if (rte->relid == LargeObjectRelationId && (perms & DB_TABLE__DELETE)) + rte->requiredPerms |= SEPGSQL_PERMS_WRITE; + + return __addEvalPgClass(selist, rte->relid, rte->inh, perms); +} + +static List *__addEvalPgAttribute(List *selist, Oid relid, bool inh, AttrNumber attno, uint32 perms) +{ + ListCell *l; + SEvalItem *se; + + foreach (l, selist) { + se = (SEvalItem *) lfirst(l); + if (se->tclass == SECCLASS_DB_COLUMN + && se->a.relid == relid + && se->a.inh == inh + && se->a.attno == attno) { + se->perms |= perms; + return selist; + } + } + /* not found */ + se = makeNode(SEvalItem); + se->tclass = SECCLASS_DB_COLUMN; + se->perms = perms; + se->a.relid = relid; + se->a.inh = inh; + se->a.attno = attno; + + return lappend(selist, se); +} + +static List *addEvalPgAttribute(List *selist, RangeTblEntry *rte, AttrNumber attno, uint32 perms) +{ + uint32 t_perms = 0; + + /* for table:{ ... } permission */ + t_perms |= (perms & DB_COLUMN__USE ? DB_TABLE__USE : 0); + t_perms |= (perms & DB_COLUMN__SELECT ? DB_TABLE__SELECT : 0); + t_perms |= (perms & DB_COLUMN__INSERT ? DB_TABLE__INSERT : 0); + t_perms |= (perms & DB_COLUMN__UPDATE ? DB_TABLE__UPDATE : 0); + selist = addEvalPgClass(selist, rte, t_perms); + + /* for 'security_context' */ + if (attno == SecurityAttributeNumber + && (perms & (DB_COLUMN__UPDATE | DB_COLUMN__INSERT))) + rte->requiredPerms |= SEPGSQL_PERMS_RELABELFROM; + + /* for 'pg_largeobject' */ + if (rte->relid == LargeObjectRelationId) { + if ((perms & DB_COLUMN__SELECT) && attno == Anum_pg_largeobject_data) + rte->requiredPerms |= SEPGSQL_PERMS_READ; + if ((perms & (DB_COLUMN__UPDATE | DB_COLUMN__INSERT)) && attno > 0) + rte->requiredPerms |= SEPGSQL_PERMS_WRITE; + } + + return __addEvalPgAttribute(selist, rte->relid, rte->inh, attno, perms); +} + +static List *addEvalPgProc(List *selist, Oid funcid, uint32 perms) +{ + ListCell *l; + SEvalItem *se; + + foreach (l, selist) { + se = (SEvalItem *) lfirst(l); + if (se->tclass == SECCLASS_DB_PROCEDURE + && se->p.funcid == funcid) { + se->perms |= perms; + return selist; + } + } + se = makeNode(SEvalItem); + se->tclass = SECCLASS_DB_PROCEDURE; + se->perms = perms; + se->p.funcid = funcid; + + return lappend(selist, se); +} + +static List *addEvalTriggerAccess(List *selist, Oid relid, bool is_inh, int cmdType) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData skey; + HeapTuple tuple; + bool checked = false; + + Assert(cmdType == CMD_INSERT || cmdType == CMD_UPDATE || cmdType == CMD_DELETE); + + rel = heap_open(TriggerRelationId, AccessShareLock); + ScanKeyInit(&skey, + Anum_pg_trigger_tgrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + scan = systable_beginscan(rel, TriggerRelidNameIndexId, + true, SnapshotNow, 1, &skey); + while (HeapTupleIsValid((tuple = systable_getnext(scan)))) { + Form_pg_trigger trigForm = (Form_pg_trigger) GETSTRUCT(tuple); + + if (!trigForm->tgenabled) + continue; + + if ((cmdType == CMD_INSERT && !TRIGGER_FOR_INSERT(trigForm->tgtype)) + || (cmdType == CMD_UPDATE && !TRIGGER_FOR_UPDATE(trigForm->tgtype)) + || (cmdType == CMD_DELETE && !TRIGGER_FOR_DELETE(trigForm->tgtype))) + continue; + + /* per STATEMENT trigger cannot refer whole of a tuple */ + if (!TRIGGER_FOR_ROW(trigForm->tgtype)) + continue; + + /* BEFORE-ROW-INSERT trigger cannot refer whole of a tuple */ + if (TRIGGER_FOR_BEFORE(trigForm->tgtype) && TRIGGER_FOR_INSERT(trigForm->tgtype)) + continue; + + selist = addEvalPgProc(selist, trigForm->tgfoid, DB_PROCEDURE__EXECUTE); + if (!checked) { + HeapTuple reltup; + Form_pg_class classForm; + AttrNumber attnum; + + reltup = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + classForm = (Form_pg_class) GETSTRUCT(reltup); + + selist = __addEvalPgClass(selist, relid, false, DB_TABLE__SELECT); + for (attnum = FirstLowInvalidHeapAttributeNumber + 1; attnum <= 0; attnum++) { + if (attnum == ObjectIdAttributeNumber && !classForm->relhasoids) + continue; + selist = __addEvalPgAttribute(selist, relid, false, attnum, DB_COLUMN__SELECT); + } + ReleaseSysCache(reltup); + + checked = true; + } + } + systable_endscan(scan); + heap_close(rel, AccessShareLock); + + if (is_inh) { + List *child_list = find_inheritance_children(relid); + ListCell *l; + + foreach(l, child_list) + selist = addEvalTriggerAccess(selist, lfirst_oid(l), is_inh, cmdType); + } + + return selist; +} + +/* ******************************************************************************* + * walkExpr() -- walk on expression tree recursively to pick up and to construct + * a SEvalItem list related to expression node. + * It is evaluated at later phase. + * *******************************************************************************/ +static List *walkVarHelper(List *selist, queryChain *qc, Var *var, int flags) +{ + RangeTblEntry *rte; + Query *query; + Node *n; + + Assert(IsA(var, Var)); + if (!qc) + elog(ERROR, "SELinux: Var node should not appear in parameter list"); + + qc = upperQueryChain(qc, var->varlevelsup); + query = getQueryFromChain(qc); + rte = list_nth(query->rtable, var->varno - 1); + Assert(IsA(rte, RangeTblEntry)); + + switch (rte->rtekind) { + case RTE_RELATION: + /* table:{select/use} and column:{select/use} */ + selist = addEvalPgAttribute(selist, rte, var->varattno, + (flags & WKFLAG_INTERNAL_USE) + ? DB_COLUMN__USE : DB_COLUMN__SELECT); + break; + case RTE_JOIN: + n = list_nth(rte->joinaliasvars, var->varattno - 1); + selist = sepgsqlWalkExpr(selist, qc, n, flags); + break; + case RTE_SUBQUERY: + /* In normal cases, rte->relid equals zero for subquery. + * If rte->relid has none-zero value, it's rewritten subquery + * for outer join handling. + */ + if (rte->relid) { + Query *sqry = rte->subquery; + RangeTblEntry *srte; + TargetEntry *tle; + Var *svar; + + Assert(sqry->commandType == CMD_SELECT); + Assert(list_length(sqry->rtable) == 1); + + srte = (RangeTblEntry *) list_nth(sqry->rtable, 0); + Assert(srte->rtekind == RTE_RELATION); + Assert(srte->relid == rte->relid); + + if (var->varattno < 1) { + ListCell *l; + bool found = false; + + foreach(l, sqry->targetList) { + TargetEntry *tle = lfirst(l); + + Assert(IsA(tle, TargetEntry)); + if (IsA(tle->expr, Const)) + continue; + + svar = (Var *) tle->expr; + Assert(IsA(svar, Var)); + if (svar->varattno == var->varattno) { + var->varattno = tle->resno; + found = true; + break; + } + } + if (!found) { + AttrNumber resno = list_length(sqry->targetList) + 1; + svar = makeVar(1, + var->varattno, + var->vartype, + var->vartypmod, + 0); + tle = makeTargetEntry((Expr *) svar, resno, NULL, false); + var->varattno = resno; + sqry->targetList = lappend(sqry->targetList, tle); + } + } else { + tle = list_nth(sqry->targetList, var->varattno - 1); + Assert(IsA(tle, TargetEntry)); + if (!IsA(tle->expr, Var)) + elog(ERROR, "SELinux: refering to dropped column (relid=%u, attno=%d)", + rte->relid, var->varattno); + svar = (Var *) tle->expr; + } + /* table:{select/use} and column:{select/use} */ + selist = addEvalPgAttribute(selist, srte, svar->varattno, + (flags & WKFLAG_INTERNAL_USE) + ? DB_COLUMN__USE : DB_COLUMN__SELECT); + } + break; + case RTE_SPECIAL: + case RTE_FUNCTION: + case RTE_VALUES: + break; + default: + elog(ERROR, "SELinux: unexpected rtekind (%d)", rte->rtekind); + break; + } + return selist; +} + +static List *walkOpExprHelper(List *selist, Oid opid) +{ + HeapTuple tuple; + Form_pg_operator oprform; + + tuple = SearchSysCache(OPEROID, + ObjectIdGetDatum(opid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for operator %u", opid); + oprform = (Form_pg_operator) GETSTRUCT(tuple); + + selist = addEvalPgProc(selist, oprform->oprcode, DB_PROCEDURE__EXECUTE); + /* NOTE: opr->oprrest and opr->oprjoin are internal use only + * and have no effect onto the data references, so we don't + * apply any checkings for them. + */ + ReleaseSysCache(tuple); + + return selist; +} + +static List *sepgsqlWalkExpr(List *selist, queryChain *qc, Node *node, int flags) +{ + if (node == NULL) + return selist; + + switch (nodeTag(node)) { + case T_Const: + case T_Param: + case T_CaseTestExpr: + case T_CoerceToDomainValue: + case T_SetToDefault: + case T_CurrentOfExpr: + /* do nothing */ + break; + case T_List: { + ListCell *l; + + foreach (l, (List *) node) + selist = sepgsqlWalkExpr(selist, qc, (Node *) lfirst(l), flags); + break; + } + case T_Var: { + selist = walkVarHelper(selist, qc, (Var *) node, flags); + break; + } + case T_FuncExpr: { + FuncExpr *func = (FuncExpr *) node; + + selist = addEvalPgProc(selist, func->funcid, DB_PROCEDURE__EXECUTE); + selist = sepgsqlWalkExpr(selist, qc, (Node *) func->args, flags); + break; + } + case T_Aggref: { + Aggref *aggref = (Aggref *) node; + + selist = addEvalPgProc(selist, aggref->aggfnoid, DB_PROCEDURE__EXECUTE); + selist = sepgsqlWalkExpr(selist, qc, (Node *) aggref->args, flags); + break; + } + case T_OpExpr: + case T_DistinctExpr: /* typedef of OpExpr */ + case T_NullIfExpr: /* typedef of OpExpr */ + { + OpExpr *op = (OpExpr *) node; + + selist = walkOpExprHelper(selist, op->opno); + selist = sepgsqlWalkExpr(selist, qc, (Node *) op->args, flags); + break; + } + case T_ScalarArrayOpExpr: { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node; + + selist = walkOpExprHelper(selist, saop->opno); + selist = sepgsqlWalkExpr(selist, qc, (Node *) saop->args, flags); + break; + } + case T_BoolExpr: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((BoolExpr *) node)->args, flags); + break; + } + case T_ArrayRef: { + ArrayRef *aref = (ArrayRef *) node; + + selist = sepgsqlWalkExpr(selist, qc, (Node *) aref->refupperindexpr, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) aref->reflowerindexpr, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) aref->refexpr, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) aref->refassgnexpr, flags); + break; + } + case T_SubLink: { + SubLink *slink = (SubLink *) node; + + Assert(IsA(slink->subselect, Query)); + selist = sepgsqlWalkExpr(selist, qc, (Node *) slink->testexpr, flags); + selist = proxyRteSubQuery(selist, qc, (Query *) slink->subselect); + break; + } + case T_SortClause: + case T_GroupClause: /* typedef of SortClause */ + { + SortClause *sort = (SortClause *) node; + Query *query = getQueryFromChain(qc); + ListCell *l; + + foreach (l, query->targetList) { + TargetEntry *tle = (TargetEntry *) lfirst(l); + Assert(IsA(tle, TargetEntry)); + if (tle->ressortgroupref == sort->tleSortGroupRef) { + selist = sepgsqlWalkExpr(selist, qc, (Node *) tle->expr, flags); + break; + } + } + break; + } + case T_CoerceToDomain: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((CoerceToDomain *) node)->arg, flags); + break; + } + case T_CaseExpr: { + CaseExpr *ce = (CaseExpr *) node; + + selist = sepgsqlWalkExpr(selist, qc, (Node *) ce->arg, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) ce->args, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) ce->defresult, flags); + break; + } + case T_CaseWhen: { + CaseWhen *casewhen = (CaseWhen *) node; + + selist = sepgsqlWalkExpr(selist, qc, (Node *) casewhen->expr, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) casewhen->result, flags); + break; + } + case T_RelabelType: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((RelabelType *) node)->arg, flags); + break; + } + case T_CoerceViaIO: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((CoerceViaIO *) node)->arg, flags); + break; + } + case T_ArrayCoerceExpr: { + ArrayCoerceExpr *ace = (ArrayCoerceExpr *) node; + + if (ace->elemfuncid != InvalidOid) + selist = addEvalPgProc(selist, ace->elemfuncid, DB_PROCEDURE__EXECUTE); + selist = sepgsqlWalkExpr(selist, qc, (Node *) ace->arg, flags); + + break; + } + case T_CoalesceExpr: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((CoalesceExpr *) node)->args, flags); + break; + } + case T_MinMaxExpr: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((MinMaxExpr *) node)->args, flags); + break; + } + case T_XmlExpr: { + XmlExpr *xe = (XmlExpr *) node; + + selist = sepgsqlWalkExpr(selist, qc, (Node *) xe->named_args, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) xe->args, flags); + break; + } + case T_NullTest: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((NullTest *) node)->arg, flags); + break; + } + case T_BooleanTest: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((BooleanTest *) node)->arg, flags); + break; + } + case T_FieldSelect: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((FieldSelect *) node)->arg, flags); + break; + } + case T_FieldStore: { + FieldStore *fstore = (FieldStore *) node; + + selist = sepgsqlWalkExpr(selist, qc, (Node *) fstore->arg, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) fstore->newvals, flags); + break; + } + case T_ArrayExpr: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((ArrayExpr *) node)->elements, flags); + break; + } + case T_RowExpr: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((RowExpr *) node)->args, flags); + break; + } + case T_RowCompareExpr: { + RowCompareExpr *rce = (RowCompareExpr *) node; + ListCell *l; + + foreach (l, rce->opnos) + selist = walkOpExprHelper(selist, lfirst_oid(l)); + selist = sepgsqlWalkExpr(selist, qc, (Node *) rce->largs, flags); + selist = sepgsqlWalkExpr(selist, qc, (Node *) rce->rargs, flags); + break; + } + case T_ConvertRowtypeExpr: { + selist = sepgsqlWalkExpr(selist, qc, + (Node *) ((ConvertRowtypeExpr *) node)->arg, flags); + break; + } + default: + elog(NOTICE, "SELinux: node with tag %d is ignored => %s", + nodeTag(node), nodeToString(node)); + break; + } + return selist; +} + +/* ******************************************************************************* + * proxyRteXXXX() -- check any relation type objects in the required query, + * including general relation, outer|inner|cross join and subquery. + * + * sepgsqlProxyQuery() is called just after query rewriting phase to constract + * a list of SEvalItems. It is attached into Query->pgaceList and evaluated by + * sepgsqlVerifyQuery() at later phase. + * *******************************************************************************/ + +static Oid fnoid_sepgsql_tuple_perm = F_SEPGSQL_TUPLE_PERMS; + +/* + * When we use LEFT OUTER JOIN, any condition defined at ON clause are not + * considered to filter tuples, so left-hand relation have to be re-written + * as a subquery to filter violated tuples. + */ +static List *makePseudoTargetList(Oid relid) { + HeapTuple reltup, atttup; + Form_pg_class classForm; + Form_pg_attribute attrForm; + AttrNumber attno, relnatts; + TargetEntry *tle; + Expr *expr; + List *targetList = NIL; + + reltup = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "SELinux: cache lookup failed for relation %u", relid); + + classForm = (Form_pg_class) GETSTRUCT(reltup); + relnatts = classForm->relnatts; + for (attno = 1; attno <= relnatts; attno++) { + atttup = SearchSysCache(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attno), + 0, 0); + if (!HeapTupleIsValid(atttup)) + elog(ERROR, "SELinux: cache lookup failed for attribute %d of relation %s", + attno, NameStr(classForm->relname)); + attrForm = (Form_pg_attribute) GETSTRUCT(atttup); + if (attrForm->attisdropped) { + expr = (Expr *) makeNullConst(INT4OID, -1); + } else { + expr = (Expr *) makeVar(1, + attno, + attrForm->atttypid, + attrForm->atttypmod, + 0); + } + tle = makeTargetEntry(expr, attno, NULL, false); + targetList = lappend(targetList, tle); + ReleaseSysCache(atttup); + + Assert(list_length(targetList) == attno); + } + ReleaseSysCache(reltup); + + return targetList; +} + +static void rewriteOuterJoinTree(Node *n, Query *query, bool is_outer_join) +{ + RangeTblRef *rtr, *srtr; + RangeTblEntry *rte, *srte; + Query *sqry; + FromExpr *sfrm; + + if (IsA(n, RangeTblRef)) { + if (!is_outer_join) + return; + + rtr = (RangeTblRef *) n; + rte = list_nth(query->rtable, rtr->rtindex - 1); + Assert(IsA(rte, RangeTblEntry)); + if (rte->rtekind != RTE_RELATION) + return; + + /* setup alternative query */ + sqry = makeNode(Query); + sqry->commandType = CMD_SELECT; + sqry->targetList = makePseudoTargetList(rte->relid); + + srte = copyObject(rte); + sqry->rtable = list_make1(srte); + + srtr = makeNode(RangeTblRef); + srtr->rtindex = 1; + + sfrm = makeNode(FromExpr); + sfrm->fromlist = list_make1(srtr); + sfrm->quals = NULL; + + sqry->jointree = sfrm; + sqry->hasSubLinks = false; + sqry->hasAggs = false; + + rte->rtekind = RTE_SUBQUERY; + rte->subquery = sqry; + } else if (IsA(n, FromExpr)) { + FromExpr *f = (FromExpr *)n; + ListCell *l; + + foreach (l, f->fromlist) + rewriteOuterJoinTree(lfirst(l), query, false); + } else if (IsA(n, JoinExpr)) { + JoinExpr *j = (JoinExpr *) n; + + rewriteOuterJoinTree(j->larg, query, + (j->jointype == JOIN_LEFT || j->jointype == JOIN_FULL)); + rewriteOuterJoinTree(j->rarg, query, + (j->jointype == JOIN_RIGHT || j->jointype == JOIN_FULL)); + } else { + elog(ERROR, "SELinux: unexpected node type (%d) in Query->jointree", nodeTag(n)); + } +} + +static List *proxyRteRelation(List *selist, queryChain *qc, int rtindex, Node **quals) +{ + Query *query; + RangeTblEntry *rte; + Relation rel; + TupleDesc tdesc; + uint32 perms; + + query = getQueryFromChain(qc); + rte = list_nth(query->rtable, rtindex - 1); + rel = relation_open(rte->relid, AccessShareLock); + tdesc = RelationGetDescr(rel); + + /* setup tclass and access vector */ + perms = rte->requiredPerms & SEPGSQL_PERMS_ALL; + + /* append sepgsql_tuple_perm(relid, record, perms) */ + if (perms) { + Var *v1, *v2, *v4; + Const *c3; + FuncExpr *func; + + /* 1st arg : Oid of the target relation */ + v1 = makeVar(rtindex, TableOidAttributeNumber, OIDOID, -1, 0); + + /* 2nd arg : Security Attribute of tuple */ + v2 = makeVar(rtindex, SecurityAttributeNumber, OIDOID, -1, 0); + + /* 3rd arg : permission set */ + c3 = makeConst(INT4OID, -1, sizeof(int32), Int32GetDatum(perms), false, true); + + /* 4th arg : RECORD of the target relation */ + v4 = makeVar(rtindex, 0, RelationGetForm(rel)->reltype, -1, 0); + + /* append sepgsql_tuple_perm */ + func = makeFuncExpr(fnoid_sepgsql_tuple_perm, BOOLOID, + list_make4(v1, v2, c3, v4), COERCE_DONTCARE); + if (*quals == NULL) { + *quals = (Node *) func; + } else { + *quals = (Node *) makeBoolExpr(AND_EXPR, list_make2(func, *quals)); + } + } + relation_close(rel, NoLock); + + return selist; +} + +static List *proxyRteOuterJoin(List *selist, queryChain *qc, Query *query) +{ + queryChain qcData; + ListCell *l; + + qcData.parent = qc; + qcData.tail = query; + qc = &qcData; + + selist = proxyRteRelation(selist, qc, 1, &query->jointree->quals); + + /* clean-up polluted RangeTblEntry */ + foreach (l, query->rtable) { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); + rte->requiredPerms &= ~SEPGSQL_PERMS_ALL; + } + + return selist; +} + +static List *__checkSelectTargets(List *selist, Query *query, Node *node) +{ + if (node == NULL) + return selist; + + if (IsA(node, RangeTblRef)) { + RangeTblRef *rtr = (RangeTblRef *) node; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable); + + switch (rte->rtekind) { + case RTE_RELATION: + selist = addEvalPgClass(selist, rte, DB_TABLE__SELECT); + break; + case RTE_SUBQUERY: + if (rte->relid) { + Query *sqry = rte->subquery; + RangeTblEntry *srte = rt_fetch(1, sqry->rtable); + + selist = addEvalPgClass(selist, srte, DB_TABLE__SELECT); + } + break; + default: + /* do nothing */ + break; + } + } else if (IsA(node, JoinExpr)) { + JoinExpr *j = (JoinExpr *) node; + + selist = __checkSelectTargets(selist, query, j->larg); + selist = __checkSelectTargets(selist, query, j->rarg); + } else if (IsA(node, FromExpr)) { + FromExpr *fm = (FromExpr *)node; + ListCell *l; + + foreach (l, fm->fromlist) + selist = __checkSelectTargets(selist, query, lfirst(l)); + } else { + elog(ERROR, "SELinux: unexpected node type (%d) at Query->fromlist", nodeTag(node)); + } + return selist; +} + +static List *proxyRteSubQuery(List *selist, queryChain *qc, Query *query) +{ + CmdType cmdType = query->commandType; + RangeTblEntry *rte = NULL; + queryChain qcData; + ListCell *l; + + /* query chain setup */ + qcData.parent = qc; + qcData.tail = query; + qc = &qcData; + + /* rewrite outer join */ + rewriteOuterJoinTree((Node *) query->jointree, query, false); + + switch (cmdType) { + case CMD_SELECT: + selist = __checkSelectTargets(selist, query, (Node *)query->jointree); + + case CMD_UPDATE: + case CMD_INSERT: + foreach (l, query->targetList) { + TargetEntry *tle = lfirst(l); + bool is_security_attr = false; + uint32 perms; + Assert(IsA(tle, TargetEntry)); + + if (tle->resjunk && tle->resname + && !strcmp(tle->resname, SECURITY_SYSATTR_NAME)) + is_security_attr = true; + + /* pure junk target entries */ + if (tle->resjunk && !is_security_attr) { + selist = sepgsqlWalkExpr(selist, qc, (Node *) tle->expr, WKFLAG_INTERNAL_USE); + continue; + } + + selist = sepgsqlWalkExpr(selist, qc, (Node *) tle->expr, 0); + + if (cmdType == CMD_SELECT) + continue; + + rte = list_nth(query->rtable, query->resultRelation - 1); + Assert(IsA(rte, RangeTblEntry) && rte->rtekind==RTE_RELATION); + perms = (cmdType == CMD_UPDATE ? DB_COLUMN__UPDATE : DB_COLUMN__INSERT); + + selist = addEvalPgAttribute(selist, + rte, + is_security_attr ? SecurityAttributeNumber : tle->resno, + perms); + } + break; + + case CMD_DELETE: + rte = list_nth(query->rtable, query->resultRelation - 1); + Assert(IsA(rte, RangeTblEntry) && rte->rtekind==RTE_RELATION); + selist = addEvalPgClass(selist, rte, DB_TABLE__DELETE); + break; + + default: + elog(ERROR, "SELinux: unexpected cmdType = %d", cmdType); + break; + } + + /* permission mark on RETURNING clause, if necessary */ + foreach (l, query->returningList) { + TargetEntry *te = lfirst(l); + Assert(IsA(te, TargetEntry)); + selist = sepgsqlWalkExpr(selist, qc, (Node *) te->expr, 0); + } + + /* permission mark on the WHERE/HAVING clause */ + selist = sepgsqlWalkExpr(selist, qc, query->jointree->quals, + WKFLAG_INTERNAL_USE); + selist = sepgsqlWalkExpr(selist, qc, query->havingQual, + WKFLAG_INTERNAL_USE); + + /* permission mark on the ORDER BY clause */ + // MEMO: no need to walk it again, it is checked as junk entries + //selist = sepgsqlWalkExpr(selist, qc, (Node *) query->sortClause, WKFLAG_INTERNAL_USE); + + /* permission mark on the GROUP BY/HAVING clause */ + // MEMO: no need to walk it again, it is checked as junk entries + //selist = sepgsqlWalkExpr(selist, qc, (Node *) query->groupClause, WKFLAG_INTERNAL_USE); + + /* permission mark on the UNION/INTERSECT/EXCEPT */ + selist = proxySetOperations(selist, qc, query->setOperations); + + /* append sepgsql_permission() on the FROM clause/USING clause + * for SELECT/UPDATE/DELETE statement. + * The target Relation of INSERT is noe necessary to append it + */ + selist = proxyJoinTree(selist, qc, (Node *) query->jointree, + &query->jointree->quals); + + /* clean-up polluted RangeTblEntry */ + foreach (l, query->rtable) { + rte = (RangeTblEntry *) lfirst(l); + rte->requiredPerms &= ~SEPGSQL_PERMS_ALL; + } + + return selist; +} + +static List *proxyJoinTree(List *selist, queryChain *qc, Node *n, Node **quals) +{ + Query *query = getQueryFromChain(qc); + + if (n == NULL) + return selist; + + if (IsA(n, RangeTblRef)) { + RangeTblRef *rtr = (RangeTblRef *) n; + RangeTblEntry *rte = list_nth(query->rtable, rtr->rtindex - 1); + Assert(IsA(rte, RangeTblEntry)); + + switch (rte->rtekind) { + case RTE_RELATION: + selist = proxyRteRelation(selist, qc, rtr->rtindex, quals); + break; + case RTE_SUBQUERY: + selist = (rte->relid + ? proxyRteOuterJoin(selist, qc, rte->subquery) + : proxyRteSubQuery(selist, qc, rte->subquery)); + break; + case RTE_FUNCTION: { + FuncExpr *f = (FuncExpr *) rte->funcexpr; + + selist = sepgsqlWalkExpr(selist, qc, (Node *) f, 0); + selist = sepgsqlWalkExpr(selist, qc, (Node *) f->args, 0); + break; + } + case RTE_VALUES: + selist = sepgsqlWalkExpr(selist, qc, (Node *) rte->values_lists, 0); + break; + default: + elog(ERROR, "SELinux: unexpected rtekinf = %d at fromList", rte->rtekind); + break; + } + } else if (IsA(n, FromExpr)) { + FromExpr *f = (FromExpr *)n; + ListCell *l; + + selist = sepgsqlWalkExpr(selist, qc, f->quals, WKFLAG_INTERNAL_USE); + foreach (l, f->fromlist) + selist = proxyJoinTree(selist, qc, lfirst(l), quals); + } else if (IsA(n, JoinExpr)) { + JoinExpr *j = (JoinExpr *) n; + + selist = sepgsqlWalkExpr(selist, qc, j->quals, WKFLAG_INTERNAL_USE); + selist = proxyJoinTree(selist, qc, j->larg, &j->quals); + selist = proxyJoinTree(selist, qc, j->rarg, &j->quals); + } else { + elog(ERROR, "SELinux: unexpected node type (%d) at Query->jointree", nodeTag(n)); + } + return selist; +} + +static List *proxySetOperations(List *selist, queryChain *qc, Node *n) +{ + Query *query = getQueryFromChain(qc); + + if (n == NULL) + return selist; + + if (IsA(n, RangeTblRef)) { + RangeTblRef *rtr = (RangeTblRef *) n; + RangeTblEntry *rte = list_nth(query->rtable, rtr->rtindex - 1); + + Assert(IsA(rte, RangeTblEntry) && rte->rtekind == RTE_SUBQUERY); + + selist = proxyRteSubQuery(selist, qc, rte->subquery); + } else if (IsA(n, SetOperationStmt)) { + SetOperationStmt *op = (SetOperationStmt *) n; + + selist = proxySetOperations(selist, qc, (Node *) op->larg); + selist = proxySetOperations(selist, qc, (Node *) op->rarg); + } else { + elog(ERROR, "SELinux: setOperationsTree contains => %s", nodeToString(n)); + } + + return selist; +} + +static List *proxyGeneralQuery(Query *query) +{ + List *selist = NIL; + + selist = proxyRteSubQuery(selist, NULL, query); + query->pgaceItem = (Node *) selist; + + return list_make1(query); +} + +static List *proxyExecuteStmt(Query *query) +{ + List *selist = NIL; + ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt; + queryChain qcData; + + Assert(nodeTag(query->utilityStmt) == T_ExecuteStmt); + + qcData.parent = NULL; + qcData.tail = query; + selist = sepgsqlWalkExpr(selist, &qcData, (Node *) estmt->params, 0); + query->pgaceItem = (Node *) selist; + + return list_make1(query); +} + +static Query *convertTruncateToDelete(Relation rel) +{ + Query *query = makeNode(Query); + RangeTblEntry *rte; + RangeTblRef *rtr; + + rte = addRangeTableEntryForRelation(NULL, rel, NULL, false, false); + rte->requiredPerms = ACL_DELETE; + rtr = makeNode(RangeTblRef); + rtr->rtindex = 1; + + query->commandType = CMD_DELETE; + query->rtable = list_make1(rte); + query->jointree = makeNode(FromExpr); + query->jointree->fromlist = list_make1(rtr); + query->jointree->quals = NULL; + query->resultRelation = rtr->rtindex; + query->hasSubLinks = false; + query->hasAggs = false; + + sepgsqlProxyQuery(query); + + return query; +} + +static List *proxyTruncateStmt(Query *query) +{ + TruncateStmt *stmt = (TruncateStmt *) query->utilityStmt; + Relation rel; + Query *subqry; + ListCell *l; + List *subquery_list = NIL, *subquery_lids = NIL; + + /* resolve the relation names */ + foreach (l, stmt->relations) { + RangeVar *rv = lfirst(l); + + rel = heap_openrv(rv, AccessShareLock); + subqry = convertTruncateToDelete(rel); + subquery_list = lappend(subquery_list, subqry); + subquery_lids = lappend_oid(subquery_lids, RelationGetRelid(rel)); + heap_close(rel, NoLock); + + elog(NOTICE, "SELinux: TRUNCATE %s is replaced unconditional DELETE", + RelationGetRelationName(rel)); + } + + if (stmt->behavior == DROP_CASCADE) { + subquery_lids = heap_truncate_find_FKs(subquery_lids); + foreach (l, subquery_lids) { + Oid relid = lfirst_oid(l); + + rel = heap_open(relid, AccessShareLock); + subqry = convertTruncateToDelete(rel); + subquery_list = lappend(subquery_list, subqry); + heap_close(rel, NoLock); + } + } + return subquery_list; +} + +List *sepgsqlProxyQuery(Query *query) +{ + List *new_list = NIL; + + switch (query->commandType) { + case CMD_SELECT: + case CMD_UPDATE: + case CMD_INSERT: + case CMD_DELETE: + new_list = proxyGeneralQuery(query); + break; + case CMD_UTILITY: + switch (nodeTag(query->utilityStmt)) { + case T_TruncateStmt: + new_list = proxyTruncateStmt(query); + break; + case T_ExecuteStmt: + new_list = proxyExecuteStmt(query); + break; + default: + new_list = list_make1(query); + /* do nothing now */ + break; + } + break; + default: + elog(ERROR, "SELinux: unexpected command type (%d)", query->commandType); + break; + } + return new_list; +} + +/* ******************************************************************************* + * verifyXXXX() -- checks any SEvalItem attached with Query->pgaceList. + * Those are generated in proxyXXXX() phase, and this evaluation is done + * just before PortalStart(). + * The reason why the checks are delayed is to handle cases when parse + * and execute are separated like PREPARE/EXECUTE statement. + * *******************************************************************************/ +static void verifyPgClassPerms(Oid relid, bool inh, uint32 perms) +{ + Form_pg_class pgclass; + HeapTuple tuple; + NameData name; + + /* prevent to modify pg_security directly */ + if (relid == SecurityRelationId + && (perms & (DB_TABLE__UPDATE | DB_TABLE__INSERT | DB_TABLE__DELETE)) != 0) + elog(ERROR, "SELinux: user cannot modify pg_security directly"); + + /* check table:{required permissions} */ + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: relation (oid=%u) does not exist", relid); + pgclass = (Form_pg_class) GETSTRUCT(tuple); + + if (pgclass->relkind != RELKIND_RELATION) { + ReleaseSysCache(tuple); + return; + } + + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_TABLE, + perms, + sepgsqlGetTupleName(RelationRelationId, tuple, &name)); + ReleaseSysCache(tuple); +} + +static void verifyPgAttributePerms(Oid relid, bool inh, AttrNumber attno, uint32 perms) +{ + HeapTuple tuple; + Form_pg_class classForm; + Form_pg_attribute attrForm; + NameData name; + + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: relation (oid=%u) does not exist", relid); + classForm = (Form_pg_class) GETSTRUCT(tuple); + if (classForm->relkind != RELKIND_RELATION) { + /* column:{ xxx } checks are applied only column within tables */ + ReleaseSysCache(tuple); + return; + } + ReleaseSysCache(tuple); + + /* 2. verify column perms */ + if (attno == 0) { + /* RECORD type permission check */ + Relation rel; + ScanKeyData skey; + SysScanDesc scan; + + ScanKeyInit(&skey, + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + rel = heap_open(AttributeRelationId, AccessShareLock); + scan = systable_beginscan(rel, AttributeRelidNumIndexId, + true, SnapshotNow, 1, &skey); + while ((tuple = systable_getnext(scan)) != NULL) { + attrForm = (Form_pg_attribute) GETSTRUCT(tuple); + if (attrForm->attisdropped || attrForm->attnum < 1) + continue; + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_COLUMN, + perms, + sepgsqlGetTupleName(AttributeRelationId, tuple, &name)); + } + systable_endscan(scan); + heap_close(rel, AccessShareLock); + + return; + } + + tuple = SearchSysCache(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attno), + 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for attribute %d of relation %u", attno, relid); + + /* check column:{required permissions} */ + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_COLUMN, + perms, + sepgsqlGetTupleName(AttributeRelationId, tuple, &name)); + ReleaseSysCache(tuple); +} + +static void verifyPgProcPerms(Oid funcid, uint32 perms) +{ + HeapTuple tuple; + NameData name; + Oid newcon; + + tuple = SearchSysCache(PROCOID, + ObjectIdGetDatum(funcid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for procedure %d", funcid); + + /* compute domain transition */ + newcon = sepgsql_avc_createcon(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_PROCESS); + if (newcon != sepgsqlGetClientContext()) + perms |= DB_PROCEDURE__ENTRYPOINT; + + /* check procedure executiong permission */ + sepgsql_avc_permission(sepgsqlGetClientContext(), + HeapTupleGetSecurity(tuple), + SECCLASS_DB_PROCEDURE, + perms, + sepgsqlGetTupleName(ProcedureRelationId, tuple, &name)); + + /* check domain transition, if necessary */ + if (newcon != sepgsqlGetClientContext()) { + sepgsql_avc_permission(sepgsqlGetClientContext(), + newcon, + SECCLASS_PROCESS, + PROCESS__TRANSITION, + NULL); + } + + ReleaseSysCache(tuple); +} + +static List *__expandPgClassInheritance(List *selist, Oid relid, uint32 perms) +{ + List *child_list = find_inheritance_children(relid); + ListCell *l; + + foreach (l, child_list) { + selist = __addEvalPgClass(selist, lfirst_oid(l), false, perms); + selist = __expandPgClassInheritance(selist, lfirst_oid(l), perms); + } + return selist; +} + +static List *__expandPgAttributeInheritance(List *selist, Oid relid, char *attname, uint32 perms) +{ + List *child_list = find_inheritance_children(relid); + ListCell *l; + + foreach (l, child_list) { + Form_pg_attribute attrForm; + HeapTuple tuple; + + if (!attname) { + /* attname == NULL means RECORD reference */ + selist = __addEvalPgAttribute(selist, lfirst_oid(l), false, 0, perms); + selist = __expandPgAttributeInheritance(selist, lfirst_oid(l), NULL, perms); + continue; + } + + tuple = SearchSysCacheAttName(lfirst_oid(l), attname); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for attribute %s of relation %u", + attname, lfirst_oid(l)); + attrForm = (Form_pg_attribute) GETSTRUCT(tuple); + selist = __addEvalPgAttribute(selist, lfirst_oid(l), false, attrForm->attnum, perms); + selist = __expandPgAttributeInheritance(selist, lfirst_oid(l), attname, perms); + + ReleaseSysCache(tuple); + } + + return selist; +} + +static List *expandSEvalListInheritance(List *selist) { + List *result = NIL; + ListCell *l; + + foreach (l, selist) { + SEvalItem *se = (SEvalItem *) lfirst(l); + + result = lappend(result, se); + switch (se->tclass) { + case SECCLASS_DB_TABLE: + if (se->c.inh) { + se->c.inh = false; + result = __expandPgClassInheritance(result, + se->c.relid, + se->perms); + } + break; + case SECCLASS_DB_COLUMN: + if (se->a.inh) { + Form_pg_attribute attrForm; + HeapTuple tuple; + + se->a.inh = false; + if (se->a.attno == 0) { + result = __expandPgAttributeInheritance(result, + se->a.relid, + NULL, + se->perms); + break; + } + tuple = SearchSysCache(ATTNUM, + ObjectIdGetDatum(se->a.relid), + Int16GetDatum(se->a.attno), + 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "SELinux: cache lookup failed for attribute %d of relation %u", + se->a.attno, se->a.relid); + attrForm = (Form_pg_attribute) GETSTRUCT(tuple); + + result = __expandPgAttributeInheritance(result, + se->a.relid, + NameStr(attrForm->attname), + se->perms); + ReleaseSysCache(tuple); + } + break; + } + } + return result; +} + +static void execVerifyQuery(List *selist) +{ + ListCell *l; + + foreach (l, selist) { + SEvalItem *se = lfirst(l); + + switch (se->tclass) { + case SECCLASS_DB_TABLE: + verifyPgClassPerms(se->c.relid, se->c.inh, se->perms); + break; + case SECCLASS_DB_COLUMN: + verifyPgAttributePerms(se->a.relid, se->a.inh, se->a.attno, se->perms); + break; + case SECCLASS_DB_PROCEDURE: + verifyPgProcPerms(se->p.funcid, se->perms); + break; + default: + elog(ERROR, "SELinux: unexpected SEvalItem (tclass: %d)", se->tclass); + break; + } + } +} + +void sepgsqlVerifyQuery(PlannedStmt *pstmt) +{ + RangeTblEntry *rte; + List *selist; + ListCell *l; + + if (!pstmt->pgaceItem) + return; + Assert(IsA(pstmt->pgaceItem, List)); + selist = (List *) pstmt->pgaceItem; + + /* expand table inheritances */ + selist = expandSEvalListInheritance(selist); + + /* add checks for access via trigger function */ + foreach(l, pstmt->resultRelations) { + Index rindex = lfirst_int(l); + + rte = rt_fetch(rindex, pstmt->rtable); + Assert(IsA(rte, RangeTblEntry)); + + selist = addEvalTriggerAccess(selist, rte->relid, rte->inh, pstmt->commandType); + } + execVerifyQuery(selist); +} + +/* ******************************************************************************* + * PGACE hooks: we cannon the following hooks in sepgsqlHooks.c because they + * refers static defined variables in sepgsqlProxy.c + * *******************************************************************************/ + +/* ---------------------------------------------------------- + * COPY TO/COPY FROM statement hooks + * ---------------------------------------------------------- */ +void sepgsqlCopyTable(Relation rel, List *attNumList, bool isFrom) +{ + List *selist = NIL; + ListCell *l; + + /* on 'COPY FROM SELECT ...' cases, any checkings are done in select.c */ + if (rel == NULL) + return; + + /* no need to check non-table relation */ + if (RelationGetForm(rel)->relkind != RELKIND_RELATION) + return; + + selist = __addEvalPgClass(selist, RelationGetRelid(rel), false, + isFrom ? DB_TABLE__INSERT : DB_TABLE__SELECT); + foreach (l, attNumList) { + AttrNumber attnum = lfirst_int(l); + + selist = __addEvalPgAttribute(selist, RelationGetRelid(rel), false, attnum, + isFrom ? DB_COLUMN__INSERT : DB_COLUMN__SELECT); + } + + /* check call trigger function */ + if (isFrom) + selist = addEvalTriggerAccess(selist, RelationGetRelid(rel), false, CMD_INSERT); + + execVerifyQuery(selist); +} + +bool sepgsqlCopyToTuple(Relation rel, List *attNumList, HeapTuple tuple) +{ + uint32 perms = SEPGSQL_PERMS_SELECT; + + /* for 'pg_largeobject' */ + if (RelationGetRelid(rel) == LargeObjectRelationId) { + ListCell *l; + + foreach (l, attNumList) { + AttrNumber attnum = lfirst_int(l); + if (attnum == Anum_pg_largeobject_data) { + perms |= SEPGSQL_PERMS_READ; + break; + } + } + } + return sepgsqlCheckTuplePerms(rel, tuple, NULL, perms, false); +} + +/* ---------------------------------------------------------- + * node copy/print hooks + * ---------------------------------------------------------- */ +Node *sepgsqlCopyObject(Node *__oldnode) { + SEvalItem *oldnode, *newnode; + + if (nodeTag(__oldnode) != T_SEvalItem) + return NULL; + oldnode = (SEvalItem *) __oldnode; + + newnode = makeNode(SEvalItem); + newnode->tclass = oldnode->tclass; + newnode->perms = oldnode->perms; + switch (oldnode->tclass) { + case SECCLASS_DB_TABLE: + newnode->c.relid = oldnode->c.relid; + newnode->c.inh = oldnode->c.inh; + break; + case SECCLASS_DB_COLUMN: + newnode->a.relid = oldnode->a.relid; + newnode->a.attno = oldnode->a.attno; + newnode->a.inh = oldnode->a.inh; + break; + case SECCLASS_DB_PROCEDURE: + newnode->p.funcid = oldnode->p.funcid; + break; + default: + elog(ERROR, "SELinux: unexpected SEvalItem node (tclass: %d)", oldnode->tclass); + break; + } + return (Node *) newnode; +} + +bool sepgsqlOutObject(StringInfo str, Node *node) { + SEvalItem *seitem = (SEvalItem *) node; + + if (nodeTag(node) != T_SEvalItem) + return false; + + appendStringInfoString(str, "SEVALITEM"); + appendStringInfo(str, ":tclass %u", seitem->tclass); + appendStringInfo(str, ":perms %u", seitem->perms); + switch(seitem->tclass) { + case SECCLASS_DB_TABLE: + appendStringInfo(str, ":c.relid %u", seitem->c.relid); + appendStringInfo(str, ":c.inh %s", seitem->c.inh ? "true" : "false"); + break; + case SECCLASS_DB_COLUMN: + appendStringInfo(str, ":a.relid %u", seitem->a.relid); + appendStringInfo(str, ":a.inh %s", seitem->a.inh ? "true" : "false"); + appendStringInfo(str, ":a.attno %u", seitem->a.attno); + break; + case SECCLASS_DB_PROCEDURE: + appendStringInfo(str, ":p.funcid %u", seitem->p.funcid); + break; + default: