diff -rpNU3 pgace/configure sepgsql/configure --- pgace/configure 2008-04-30 11:22:08.000000000 +0900 +++ sepgsql/configure 2008-04-30 11:27:43.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-float4-byval disable float4 passed by value --disable-float8-byval disable float8 passed by value --disable-largefile omit support for large files @@ -5101,6 +5103,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 # @@ -26220,6 +26331,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 @@ -26230,7 +26342,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 @@ -26272,6 +26383,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 @@ -26322,7 +26434,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-04-30 11:22:08.000000000 +0900 +++ sepgsql/configure.in 2008-04-30 11:27:43.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-19 10:42:28.000000000 +0900 +++ sepgsql/src/backend/Makefile 2008-03-19 10:49:25.000000000 +0900 @@ -34,6 +34,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-04-02 16:28:27.000000000 +0900 @@ -0,0 +1,668 @@ +/* + * 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 "utils/tqual.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-04-02 16:28:27.000000000 +0900 @@ -0,0 +1,588 @@ +/* + * 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/tqual.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-04-30 09:46:31.000000000 +0900 @@ -0,0 +1,1478 @@ +/* + * 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/clauses.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" +#include "utils/tqual.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; + +typedef struct queryStack { + struct queryStack *parent; + Query *query; +} queryStack; + +typedef struct sepgsqlWalkerContext { + /* List of SEvalItem */ + List *selist; + + struct queryStack *qstack; + + /* flags */ + bool is_internal_use; +} sepgsqlWalkerContext; + +/* static definitions for proxy functions */ +static void proxyRteRelation(sepgsqlWalkerContext *swc, int rtindex, Node **quals); +static void proxyRteSubQuery(sepgsqlWalkerContext *swc, Query *query); +static void proxyJoinTree(sepgsqlWalkerContext *swc, Node *node, Node **quals); +static void proxySetOperations(sepgsqlWalkerContext *swc, Node *node); + +/* static */ +static bool sepgsqlExprWalker(Node *node, sepgsqlWalkerContext *swc); + +/* ----------------------------------------------------------- + * 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; +} + +/* ******************************************************************************* + * sepgsqlExprWalker() -- 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 void walkVarHelper(sepgsqlWalkerContext *swc, Var *var) +{ + RangeTblEntry *rte; + queryStack *qstack; + Query *query; + Node *node; + int lv; + + Assert(IsA(var, Var)); + /* resolve external Var reference */ + qstack = swc->qstack; + lv = var->varlevelsup; + while (lv > 0) { + Assert(!!qstack->parent); + qstack = qstack->parent; + lv--; + } + query = qstack->query; + rte = rt_fetch(var->varno, query->rtable); + Assert(IsA(rte, RangeTblEntry)); + + switch (rte->rtekind) { + case RTE_RELATION: + /* table:{select/use} and column:{select/use} */ + swc->selist = addEvalPgAttribute(swc->selist, rte, var->varattno, + swc->is_internal_use + ? DB_COLUMN__USE : DB_COLUMN__SELECT); + break; + + case RTE_JOIN: + node = list_nth(rte->joinaliasvars, var->varattno - 1); + sepgsqlExprWalker(node, swc); + 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} */ + swc->selist = addEvalPgAttribute(swc->selist, srte, svar->varattno, + swc->is_internal_use + ? DB_COLUMN__USE : DB_COLUMN__SELECT); + } + break; + + case RTE_SPECIAL: + case RTE_FUNCTION: + case RTE_VALUES: + /* do nothing */ + break; + + default: + elog(ERROR, "SELinux: unexpected rtekind (%d)", rte->rtekind); + break; + } +} + +static void walkOpExprHelper(sepgsqlWalkerContext *swc, 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); + + swc->selist = addEvalPgProc(swc->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); +} + +static bool sepgsqlExprWalker(Node *node, sepgsqlWalkerContext *swc) +{ + if (node == NULL) + return false; + + switch (nodeTag(node)) { + case T_Var: + walkVarHelper(swc, (Var *) node); + break; + + case T_FuncExpr: + swc->selist = addEvalPgProc(swc->selist, ((FuncExpr *) node)->funcid, DB_PROCEDURE__EXECUTE); + break; + + case T_Aggref: + swc->selist = addEvalPgProc(swc->selist, ((Aggref *) node)->aggfnoid, DB_PROCEDURE__EXECUTE); + break; + + case T_OpExpr: + case T_DistinctExpr: /* typedef of OpExpr */ + case T_NullIfExpr: /* typedef of OpExpr */ + walkOpExprHelper(swc, ((OpExpr *) node)->opno); + break; + + case T_ScalarArrayOpExpr: + walkOpExprHelper(swc, ((ScalarArrayOpExpr *) node)->opno); + break; + + case T_SubLink: { + SubLink *slink = (SubLink *) node; + + Assert(IsA(slink->subselect, Query)); + proxyRteSubQuery(swc, (Query *) slink->subselect); + break; + } + case T_ArrayCoerceExpr: { + ArrayCoerceExpr *ace = (ArrayCoerceExpr *) node; + + if (ace->elemfuncid != InvalidOid) + swc->selist = addEvalPgProc(swc->selist, ace->elemfuncid, DB_PROCEDURE__EXECUTE); + break; + } + case T_RowCompareExpr: { + RowCompareExpr *rce = (RowCompareExpr *) node; + ListCell *l; + + foreach (l, rce->opnos) + walkOpExprHelper(swc, lfirst_oid(l)); + break; + } + default: + /* do nothing here */ + break; + } + + return expression_tree_walker(node, sepgsqlExprWalker, (void *) swc); +} + +static bool sepgsqlExprWalkerFlags(Node *node, sepgsqlWalkerContext *swc, bool is_internal_use) +{ + bool saved_is_internal_use = swc->is_internal_use; + bool rc; + + swc->is_internal_use = is_internal_use; + rc = sepgsqlExprWalker(node, swc); + swc->is_internal_use = saved_is_internal_use; + + return rc; +} + +/* ******************************************************************************* + * 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 void proxyRteRelation(sepgsqlWalkerContext *swc, int rtindex, Node **quals) +{ + Query *query = swc->qstack->query; + RangeTblEntry *rte; + Relation rel; + TupleDesc tdesc; + uint32 perms; + + rte = rt_fetch(rtindex, query->rtable); + 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, AccessShareLock); +} + +static void proxyRteOuterJoin(sepgsqlWalkerContext *swc, Query *query) +{ + struct queryStack qsData; + ListCell *l; + + qsData.parent = swc->qstack; + qsData.query = query; + swc->qstack = &qsData; + + proxyRteRelation(swc, 1, &query->jointree->quals); + + /* clean-up polluted RangeTblEntry */ + foreach (l, query->rtable) { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); + rte->requiredPerms &= ~SEPGSQL_PERMS_ALL; + } + swc->qstack = qsData.parent; +} + +static void __checkSelectTargets(sepgsqlWalkerContext *swc, Query *query, Node *node) +{ + if (node == NULL) + return; + + if (IsA(node, RangeTblRef)) { + RangeTblRef *rtr = (RangeTblRef *) node; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable); + + switch (rte->rtekind) { + case RTE_RELATION: + swc->selist = addEvalPgClass(swc->selist, rte, DB_TABLE__SELECT); + break; + case RTE_SUBQUERY: + if (rte->relid) { + Query *sqry = rte->subquery; + RangeTblEntry *srte = rt_fetch(1, sqry->rtable); + + swc->selist = addEvalPgClass(swc->selist, srte, DB_TABLE__SELECT); + } + break; + default: + /* do nothing */ + break; + } + } else if (IsA(node, JoinExpr)) { + __checkSelectTargets(swc, query, ((JoinExpr *) node)->larg); + __checkSelectTargets(swc, query, ((JoinExpr *) node)->rarg); + + } else if (IsA(node, FromExpr)) { + ListCell *l; + + foreach (l, ((FromExpr *)node)->fromlist) + __checkSelectTargets(swc, query, lfirst(l)); + } else { + elog(ERROR, "SELinux: unexpected node type (%d) at Query->fromlist", nodeTag(node)); + } +} + +static void proxyRteSubQuery(sepgsqlWalkerContext *swc, Query *query) +{ + CmdType cmdType = query->commandType; + RangeTblEntry *rte = NULL; + struct queryStack qsData; + ListCell *l; + + /* push a query to queryStack */ + qsData.parent = swc->qstack; + qsData.query = query; + swc->qstack = &qsData; + + /* rewrite outer join */ + rewriteOuterJoinTree((Node *) query->jointree, query, false); + + switch (cmdType) { + case CMD_SELECT: + __checkSelectTargets(swc, query, (Node *)query->jointree); + + case CMD_UPDATE: + case CMD_INSERT: + foreach (l, query->targetList) { + TargetEntry *tle = lfirst(l); + bool is_security_attr = false; + + 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) { + sepgsqlExprWalkerFlags((Node *) tle->expr, swc, true); + continue; + } + + sepgsqlExprWalkerFlags((Node *) tle->expr, swc, false); + + if (cmdType == CMD_SELECT) + continue; + + rte = list_nth(query->rtable, query->resultRelation - 1); + Assert(IsA(rte, RangeTblEntry) && rte->rtekind==RTE_RELATION); + + swc->selist = addEvalPgAttribute(swc->selist, rte, + is_security_attr ? SecurityAttributeNumber : tle->resno, + cmdType == CMD_UPDATE ? DB_COLUMN__UPDATE : DB_COLUMN__INSERT); + } + break; + + case CMD_DELETE: + rte = rt_fetch(query->resultRelation, query->rtable); + Assert(IsA(rte, RangeTblEntry) && rte->rtekind==RTE_RELATION); + swc->selist = addEvalPgClass(swc->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)); + sepgsqlExprWalkerFlags((Node *) te->expr, swc, false); + } + + /* permission mark on the WHERE/HAVING clause */ + sepgsqlExprWalkerFlags(query->jointree->quals, swc, true); + sepgsqlExprWalkerFlags(query->havingQual, swc, true); + + /* 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 */ + proxySetOperations(swc, 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 + */ + proxyJoinTree(swc, (Node *) query->jointree, &query->jointree->quals); + + /* clean-up polluted RangeTblEntry */ + foreach (l, query->rtable) { + rte = (RangeTblEntry *) lfirst(l); + rte->requiredPerms &= ~SEPGSQL_PERMS_ALL; + } + + /* pop a query to queryStack */ + swc->qstack = qsData.parent; +} + +static void proxyJoinTree(sepgsqlWalkerContext *swc, Node *node, Node **quals) +{ + Query *query = swc->qstack->query; + + if (node == NULL) + return; + + if (IsA(node, RangeTblRef)) { + RangeTblRef *rtr = (RangeTblRef *) node; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable); + Assert(IsA(rte, RangeTblEntry)); + + switch (rte->rtekind) { + case RTE_RELATION: + proxyRteRelation(swc, rtr->rtindex, quals); + break; + + case RTE_SUBQUERY: + if (rte->relid) { + proxyRteOuterJoin(swc, rte->subquery); + } else { + proxyRteSubQuery(swc, rte->subquery); + } + break; + + case RTE_FUNCTION: + sepgsqlExprWalkerFlags(rte->funcexpr, swc, false); + break; + + case RTE_VALUES: + sepgsqlExprWalkerFlags((Node *) rte->values_lists, swc, false); + break; + + default: + elog(ERROR, "SELinux: unexpected rtekinf = %d at fromList", rte->rtekind); + break; + } + } else if (IsA(node, FromExpr)) { + FromExpr *f = (FromExpr *)node; + ListCell *l; + + sepgsqlExprWalkerFlags(f->quals, swc, true); + foreach (l, f->fromlist) + proxyJoinTree(swc, lfirst(l), quals); + } else if (IsA(node, JoinExpr)) { + JoinExpr *j = (JoinExpr *) node; + + sepgsqlExprWalkerFlags(j->quals, swc, true); + proxyJoinTree(swc, j->larg, &j->quals); + proxyJoinTree(swc, j->rarg, &j->quals); + } else { + elog(ERROR, "SELinux: unexpected node type (%d) at Query->jointree", nodeTag(node)); + } +} + +static void proxySetOperations(sepgsqlWalkerContext *swc, Node *node) +{ + Query *query = swc->qstack->query; + + if (node == NULL) + return; + + if (IsA(node, RangeTblRef)) { + RangeTblRef *rtr = (RangeTblRef *) node; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable); + + Assert(IsA(rte, RangeTblEntry) && rte->rtekind == RTE_SUBQUERY); + proxyRteSubQuery(swc, rte->subquery); + + } else if (IsA(node, SetOperationStmt)) { + proxySetOperations(swc, ((SetOperationStmt *) node)->larg); + proxySetOperations(swc, ((SetOperationStmt *) node)->rarg); + + } else { + elog(ERROR, "SELinux: setOperationsTree contains => %s", nodeToString(node)); + } +} + +static List *proxyGeneralQuery(Query *query) +{ + sepgsqlWalkerContext swcData; + memset(&swcData, 0, sizeof(sepgsqlWalkerContext)); + + proxyRteSubQuery(&swcData, query); + query->pgaceItem = (Node *) swcData.selist; + + return list_make1(query); +} + +static List *proxyExecuteStmt(Query *query) +{ + ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt; + sepgsqlWalkerContext swcData; + queryStack qsData; + + Assert(nodeTag(query->utilityStmt) == T_ExecuteStmt); + + qsData.parent = NULL; + qsData.query = query; + memset(&swcData, 0, sizeof(sepgsqlWalkerContext)); + swcData.qstack = &qsData; + + sepgsqlExprWalkerFlags((Node *) estmt->params, &swcData, false); + query->pgaceItem = (Node *) swcData.selist; + + return list_make1(query); +} + +static List *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; + + return sepgsqlProxyQuery(query); +} + +static List *proxyTruncateStmt(Query *query) +{ + TruncateStmt *stmt = (TruncateStmt *) query->utilityStmt; + Relation rel; + 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); + subquery_list = list_concat(subquery_list, + convertTruncateToDelete(rel)); + subquery_lids = lappend_oid(subquery_lids, + RelationGetRelid(rel)); + heap_close(rel, AccessShareLock); + + 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); + subquery_list = lappend(subquery_list, + convertTruncateToDelete(rel)); + heap_close(rel, AccessShareLock); + } + } + 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: + elog(ERROR, "SELinux: unexpected SEvalItem node (tclass: %d)", seitem->tclass); + break; + } + return true; +} + +void *sepgsqlReadObject(char *token) +{ + SEvalItem *seitem; + int length; + + if (strcmp(token, "SEVALITEM")) + return NULL; + + seitem = makeNode(SEvalItem); + + /* :tclass */ + token = pg_strtok(&length); + token = pg_strtok(&length); + seitem->tclass = atoi(token); + + /* :perms */ + token = pg_strtok(&length); + token = pg_strtok(&length); + seitem->perms = (unsigned int) strtoul(token, NULL, 10); + + switch (seitem->tclass) { + case SECCLASS_DB_TABLE: + /* :c.relid */ + token = pg_strtok(&length); + token = pg_strtok(&length); + seitem->c.relid = (unsigned int) strtoul(token, NULL, 10); + + /* :c.inh */ + token = pg_strtok(&length); + token = pg_strtok(&length); + seitem->c.inh = (strcmp(token, "true") == 0 ? true : false); + break; + + case SECCLASS_DB_COLUMN: + /* :a.relid */ + token = pg_strtok(&length); + token = pg_strtok(&length); + seitem->a.relid = (unsigned int) strtoul(token, NULL, 10); + + /* :a.inh */ + token = pg_strtok(&length); + token = pg_strtok(&length); + seitem->a.inh = (strcmp(token, "true") == 0 ? true : false); + + /* :a.attno */ + token = pg_strtok(&length); + token = pg_strtok(&length); + seitem->a.attno = atoi(token); + break; + + case SECCLASS_DB_PROCEDURE: + /* :p.funcid */ + token = pg_strtok(&length); + token = pg_strtok(&length); + seitem->p.funcid = (unsigned int) strtoul(token, NULL, 10); + break; + + default: + elog(ERROR, "SELinux: unexpected SEvalItem node (tclass: %d)", seitem->tclass); + break; + } + return (void *) seitem; +} + +/* ---------------------------------------------------------- + * special cases in foreign key constraint + * ---------------------------------------------------------- */ +Oid sepgsqlPreparePlanCheck(Relation rel) { + Oid pgace_saved = fnoid_sepgsql_tuple_perm; + fnoid_sepgsql_tuple_perm = F_SEPGSQL_TUPLE_PERMS_ABORT; + return pgace_saved; +} + +void sepgsqlRestorePlanCheck(Relation rel, Oid pgace_saved) { + fnoid_sepgsql_tuple_perm = pgace_saved; +} diff -rpNU3 pgace/src/include/catalog/pg_proc.h sepgsql/src/include/catalog/pg_proc.h --- pgace/src/include/catalog/pg_proc.h 2008-04-30 11:22:08.000000000 +0900 +++ sepgsql/src/include/catalog/pg_proc.h 2008-04-30 11:27:43.000000000 +0900 @@ -4137,6 +4137,11 @@ DATA(insert OID = 3409 ( security_label_ DATA(insert OID = 3410 ( lo_get_security PGNSP PGUID 12 1 0 f f t f v 1 3403 "26" _null_ _null_ _null_ lo_get_security - _null_ _null_ )); DATA(insert OID = 3411 ( lo_set_security PGNSP PGUID 12 1 0 f f t f v 2 16 "26 3403" _null_ _null_ _null_ lo_set_security - _null_ _null_ )); +/* SE-PostgreSQL related function */ +DATA(insert OID = 3420 ( sepgsql_getcon PGNSP PGUID 12 1 0 f f t f v 0 3403 "" _null_ _null_ _null_ sepgsql_getcon - _null_ _null_ )); +DATA(insert OID = 3421 ( sepgsql_tuple_perms PGNSP PGUID 12 9999 0 f f t f v 4 16 "26 3403 23 2249" _null_ _null_ _null_ sepgsql_tuple_perms - _null_ _null_ )); +DATA(insert OID = 3422 ( sepgsql_tuple_perms_abort PGNSP PGUID 12 9999 0 f f t f v 4 16 "26 3403 23 2249" _null_ _null_ _null_ sepgsql_tuple_perms_abort - _null_ _null_ )); + /* enum related procs */ DATA(insert OID = 3504 ( anyenum_in PGNSP PGUID 12 1 0 f f t f i 1 3500 "2275" _null_ _null_ _null_ anyenum_in - _null_ _null_ )); DESCR("I/O"); diff -rpNU3 pgace/src/include/pg_config.h.in sepgsql/src/include/pg_config.h.in --- pgace/src/include/pg_config.h.in 2008-04-21 13:09:57.000000000 +0900 +++ sepgsql/src/include/pg_config.h.in 2008-04-21 13:27:26.000000000 +0900 @@ -373,6 +373,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SECURITY_PAM_APPL_H +/* Define to 1 if you enable NSA SELinux support */ +#undef HAVE_SELINUX + /* Define to 1 if you have the `setproctitle' function. */ #undef HAVE_SETPROCTITLE diff -rpNU3 pgace/src/include/security/sepgsql.h sepgsql/src/include/security/sepgsql.h --- pgace/src/include/security/sepgsql.h 1970-01-01 09:00:00.000000000 +0900 +++ sepgsql/src/include/security/sepgsql.h 2008-02-04 17:40:05.000000000 +0900 @@ -0,0 +1,140 @@ +#ifndef SEPGSQL_H +#define SEPGSQL_H + +/* system catalogs */ +#include "catalog/pg_security.h" +#include "lib/stringinfo.h" +#include "nodes/nodes.h" +#include "nodes/parsenodes.h" +#include "storage/large_object.h" + +#include +#include +#include + +/* + * Permission codes of internal representation + */ +#define SEPGSQL_PERMS_USE (1UL << (N_ACL_RIGHTS + 0)) +#define SEPGSQL_PERMS_SELECT (1UL << (N_ACL_RIGHTS + 1)) +#define SEPGSQL_PERMS_UPDATE (1UL << (N_ACL_RIGHTS + 2)) +#define SEPGSQL_PERMS_INSERT (1UL << (N_ACL_RIGHTS + 3)) +#define SEPGSQL_PERMS_DELETE (1UL << (N_ACL_RIGHTS + 4)) +#define SEPGSQL_PERMS_RELABELFROM (1UL << (N_ACL_RIGHTS + 5)) +#define SEPGSQL_PERMS_RELABELTO (1UL << (N_ACL_RIGHTS + 6)) +#define SEPGSQL_PERMS_READ (1UL << (N_ACL_RIGHTS + 7)) +#define SEPGSQL_PERMS_WRITE (1UL << (N_ACL_RIGHTS + 8)) +#define SEPGSQL_PERMS_ALL ((SEPGSQL_PERMS_WRITE << 1) - SEPGSQL_PERMS_USE) + +/* + * The implementation of PGACE/SE-PostgreSQL hooks + */ + +/* Initialize / Finalize related hooks */ +extern Size sepgsqlShmemSize(void); +extern void sepgsqlInitialize(bool is_bootstrap); +extern int sepgsqlInitializePostmaster(void); +extern void sepgsqlFinalizePostmaster(void); + +/* SQL proxy hooks */ +extern List *sepgsqlProxyQuery(Query *query); +extern void sepgsqlVerifyQuery(PlannedStmt *pstmt); + +/* HeapTuple modification hooks */ +extern bool sepgsqlHeapTupleInsert(Relation rel, HeapTuple tuple, + bool is_internal, bool with_returning); +extern bool sepgsqlHeapTupleUpdate(Relation rel, ItemPointer otid, HeapTuple newtup, + bool is_internal, bool with_returning); +extern bool sepgsqlHeapTupleDelete(Relation rel, ItemPointer otid, + bool is_internal, bool with_returning); + +/* Extended SQL statement hooks */ +extern DefElem *sepgsqlGramSecurityItem(char *defname, char *value); +extern bool sepgsqlIsGramSecurityItem(DefElem *defel); +extern void sepgsqlGramCreateRelation(Relation rel, HeapTuple tuple, DefElem *defel); +extern void sepgsqlGramCreateAttribute(Relation rel, HeapTuple tuple, DefElem *defel); +extern void sepgsqlGramAlterRelation(Relation rel, HeapTuple tuple, DefElem *defel); +extern void sepgsqlGramAlterAttribute(Relation rel, HeapTuple tuple, DefElem *defel); +extern void sepgsqlGramCreateDatabase(Relation rel, HeapTuple tuple, DefElem *defel); +extern void sepgsqlGramAlterDatabase(Relation rel, HeapTuple tuple, DefElem *defel); +extern void sepgsqlGramCreateFunction(Relation rel, HeapTuple tuple, DefElem *defel); +extern void sepgsqlGramAlterFunction(Relation rel, HeapTuple tuple, DefElem *defel); + +/* DATABASE related hooks */ +extern void sepgsqlSetDatabaseParam(const char *name, char *argstring); +extern void sepgsqlGetDatabaseParam(const char *name); + +/* FUNCTION related hooks */ +extern void sepgsqlCallFunction(FmgrInfo *finfo, bool with_perm_check); +extern bool sepgsqlCallFunctionTrigger(FmgrInfo *finfo, TriggerData *tgdata); +extern Oid sepgsqlPreparePlanCheck(Relation rel); +extern void sepgsqlRestorePlanCheck(Relation rel, Oid pgace_saved); + +/* TABLE related hooks */ +extern void sepgsqlLockTable(Oid relid); +extern bool sepgsqlAlterTable(Relation rel, AlterTableCmd *cmd); + +/* COPY TO/COPY FROM statement hooks */ +extern void sepgsqlCopyTable(Relation rel, List *attnumlist, bool is_from); +extern bool sepgsqlCopyToTuple(Relation rel, List *attnumlist, HeapTuple tuple); + +/* Loadable shared library module hooks */ +extern void sepgsqlLoadSharedModule(const char *filename); + +/* Binary Large Object (BLOB) hooks */ +extern void sepgsqlLargeObjectGetSecurity(HeapTuple tuple); +extern void sepgsqlLargeObjectSetSecurity(HeapTuple tuple, Oid lo_security); +extern void sepgsqlLargeObjectCreate(Relation rel, HeapTuple tuple); +extern void sepgsqlLargeObjectDrop(Relation rel, HeapTuple tuple); +extern void sepgsqlLargeObjectRead(Relation rel, HeapTuple tuple); +extern void sepgsqlLargeObjectWrite(Relation rel, HeapTuple newtup, HeapTuple oldtup); +extern void sepgsqlLargeObjectTruncate(Relation rel, Oid loid, HeapTuple headtup); +extern void sepgsqlLargeObjectImport(void); +extern void sepgsqlLargeObjectExport(void); + +/* Security Label hooks */ +extern char *sepgsqlSecurityLabelIn(char *context); +extern char *sepgsqlSecurityLabelOut(char *context); +extern char *sepgsqlSecurityLabelCheckValid(char *context); +extern char *sepgsqlSecurityLabelOfLabel(char *context); + +/* Extended node type hooks */ +extern Node *sepgsqlCopyObject(Node *node); +extern bool sepgsqlOutObject(StringInfo str, Node *node); +extern void *sepgsqlReadObject(char *token); + +/* + * SE-PostgreSQL core functions + * src/backend/security/sepgsql/core.c + */ +extern bool sepgsqlIsEnabled(void); +extern Oid sepgsqlGetServerContext(void); +extern Oid sepgsqlGetClientContext(void); +extern void sepgsqlSetClientContext(Oid new_ctx); +extern Oid sepgsqlGetDatabaseContext(void); +extern char *sepgsqlGetDatabaseName(void); + +/* userspace access vector cache related */ +extern void sepgsql_avc_permission(Oid ssid, Oid tsid, uint16 tclass, + uint32 perms, char *objname); +extern bool sepgsql_avc_permission_noabort(Oid ssid, Oid tsid, uint16 tclass, + uint32 perms, char *objname); +extern Oid sepgsql_avc_createcon(Oid ssid, Oid tsid, uint16 tclass); +extern Oid sepgsql_avc_relabelcon(Oid ssid, Oid tsid, uint16 tclass); + +/* + * SE-PostgreSQL permission evaluation related + * src/backend/security/sepgsql/permission.c + */ +extern char *sepgsqlGetTupleName(Oid relid, HeapTuple tuple, NameData *name); +extern Oid sepgsqlComputeImplicitContext(Relation rel, HeapTuple tuple); +extern bool sepgsqlCheckTuplePerms(Relation rel, HeapTuple tuple, HeapTuple oldtup, + uint32 perms, bool abort); +/* + * SE-PostgreSQL SQL FUNCTIONS + */ +extern Datum sepgsql_getcon(PG_FUNCTION_ARGS); +extern Datum sepgsql_tuple_perms(PG_FUNCTION_ARGS); +extern Datum sepgsql_tuple_perms_abort(PG_FUNCTION_ARGS); + +#endif /* SEPGSQL_H */