Prep object creation hooks, and related sepgsql updates

Started by Kohei KaiGaiabout 14 years ago17 messages
#1Kohei KaiGai
kaigai@kaigai.gr.jp
4 attachment(s)

These attached patches were getting rebased to the latest master and cosmetic
changes towrad upcoming commit-fest Nov.
Please apply these patches in order of Part-1, Part-2, Part-3 then Part-4.

We still don't have clear direction of the way to implement external permission
checks on object creation time. So, please consider these patches are on the
proof-of-concept stage; using prep-creation-hook to permission checks.

The reasons why existing post-creation-hook is not perfect to
implement permission
check on object creation time are:

(A) post-creation-hook is also invoked when we don't want to apply
permission checks.
For example, make_new_heap() invokes heap_create_with_catalog() to create
a temporary table for internal stuff as a part of CLUSTER, VACUUME FULL or some
of ALTER TABLE statements. However, it is not an option to deliver a
flag to show
whether this context needs permission checks, because it depends on
the knowledge
of individual security modules.

(B) system catalog does not contain all the necessary information for
permission checks.
Only pg_database hits this case. Existing DAC checks createdb
privilege and ownership
of the source database (if not templace), however, pg_database does
not contain oid of
the source database, so it is unavailable to apply equivalent
permission checks on the
external security modules.

Thus, the current solution is put new hooks (prep-creation) around
existing permission
checks with arguments being used to existing DAC checks commonly.
Since the argument has a pointer of the private opaque to be delivered
to post-creation
hooks. In sepgsql case, it is used to inform post-creation hook
security label to be
assigned on the new object being constructed.

Even though this approach enables to solve the issue, I'm not sure
whether we should
have prep-creation hooks for each object types in an automatic manner.
We currently have 30 of post-creation hooks for each object types,
however, most of
them does not hit the case of above either (A) or (B).
I guess pg_database, pg_class and pg_trigger (it takes "isInternal"
flag to avoid
nonsense permission checks) are the only case that hits post-creation hook is
not enough to implement correct permission checks.
So, I'm not sure to add prep-creation hook on rest of (90% of ) object
types, although
here is few strong reason to split creation-hook into two parts.

Thanks,

2011/11/12 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2011/11/8 Robert Haas <robertmhaas@gmail.com>:

On Mon, Nov 7, 2011 at 12:20 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

If sepgsql would apply permission checks db_procedure:{install} on the
OAT_POST_CREATE hook based on the funcion-oid within new entry of
system catalog, we can relocate OAT_PREP_CREATE hook more conceptually
right place, such as just after the pg_namespace_aclcheck() of DefineType().
On the other hand, we may need to answer why these information are NOT
delivered on the OAT_PREP_CREATE hook without knowledge of sepgsql
internal.

I'm having a hard time understanding this.

Can you strip this patch down so it just applies to a single object
type (tables, maybe, or functions, or whatever you like) and then
submit the corresponding sepgsql changes with it?  Just as a demo
patch, so I can understand where you're trying to go with this.

I tried to strip the previous patch into small portion per object types.
Part-1 for database, Part-2 for schema, Part-3 for relations, and
Part-4 for functions.

The basic idea is the prep-creation hook informs the sepgsql module
properties of the new object being constructed. According to the
previous discussion, all the arguments are commonly used to existing
DAC checks also. Then, this hook allows to write back its private
opaque to be delivered to the post-creation hook; likely used to deliver
security label of the new object to be assigned.

However, I become unsure whether it is a good idea to put prep-creation
hook on all the object, because it takes many boring interface changes
to deliver private datum, and we're probably able to implement similar
stuff with post-creation hook except for a few object types.

I guess the following (A) and (B) are the only case that needs prep-
creation hooks for permission checks. Elsewhere, sepgsql will be
able to make its decision based on the entry of system catalogs
on post-creation hook.

(A) In the case when we want to apply checks based on information
that is not contained within system catalogs.
E.g, Oid of source database on CREATE DATABASE. Existing DAC
checks ownership of the database, if not template.

(B) In the case when we want to distinguish code path between user's
query and system internal stuff.
E.g, heap_create_with_catalog() is also called by make_new_heap()
as a part of ALTER TABLE, but it is quite internal stuff, so not suitable
to apply permission checks here.

It seems to me, using post-creation hooks makes the patch mode simple
to implement permission checks; except for above two object types.
So, I'd like to adopt approach to put prep-creation hooks on limited number
of object types, not symmetric with post-creation hook.
How about your opinion about this?

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-prep-creation-hook-part-1.v1.database.patchapplication/octet-stream; name=pgsql-v9.2-prep-creation-hook-part-1.v1.database.patchDownload
diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 7f15d9c..ed3817b 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -15,40 +15,72 @@
 #include "commands/seclabel.h"
 #include "sepgsql.h"
 
-void
-sepgsql_database_post_create(Oid databaseId)
+/*
+ * sepgsql_database_prep_create
+ *
+ * This routine computes default security label and checks permissions to
+ * create a new database.
+ */
+const char *
+sepgsql_database_prep_create(const char *datname,
+							 Oid datsourceId,
+							 Oid tablespaceId)
 {
 	char   *scontext = sepgsql_get_client_label();
 	char   *tcontext;
 	char   *ncontext;
+	char	audit_name[NAMEDATALEN + 16];
 	ObjectAddress	object;
 
-	/*
-	 * Compute a default security label of the newly created database
-	 * based on a pair of security label of client and source database.
-	 *
-	 * XXX - Right now, this logic uses "template1" as its source, because
-	 * here is no way to know the Oid of source database.
-	 */
+	/* check db_database:{getattr} permission on the source database */
 	object.classId = DatabaseRelationId;
-	object.objectId = TemplateDbOid;
+	object.objectId = datsourceId;;
 	object.objectSubId = 0;
-	tcontext = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG);
+	sepgsql_avc_check_perms(&object,
+							SEPG_CLASS_DB_DATABASE,
+							SEPG_DB_DATABASE__GETATTR,
+							getObjectDescription(&object),
+							true);
+	/*
+	 * Compute a default security label of the new database being
+	 * constructed; based on a pair of security label of client and
+	 * source database.
+	 */
+	tcontext = sepgsql_get_label(DatabaseRelationId, datsourceId, 0);
 
+	/*
+	 * XXX - upcoming libselinux supports case handling when
+	 * a new object that has a particular name.
+	 */
 	ncontext = sepgsql_compute_create(scontext, tcontext,
 									  SEPG_CLASS_DB_DATABASE);
 
-	/*
-	 * Assign the default security label on the new database
-	 */
+	/* check db_database:{create} permission */
+	snprintf(audit_name, sizeof(audit_name), "database: %s", datname);
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_DATABASE,
+								  SEPG_DB_DATABASE__CREATE,
+								  audit_name,
+								  true);
+
+	return ncontext;
+}
+
+/*
+ * sepgsql_database_post_create
+ *
+ * This routine assigns default security label of the database.
+ */
+void
+sepgsql_database_post_create(Oid databaseId, const char *seclabel)
+{
+	ObjectAddress	object;
+
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
 
-	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
-
-	pfree(ncontext);
-	pfree(tcontext);
+	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, seclabel);
 }
 
 /*
diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
new file mode 100644
index 0000000..95fc4f1
--- /dev/null
+++ b/contrib/sepgsql/expected/create.out
@@ -0,0 +1,19 @@
+--
+-- Regression Test for Creation of Object Permission Checks
+--
+-- confirm required permissions using audit messages
+SELECT sepgsql_getcon();	-- confirm client privilege
+              sepgsql_getcon               
+-------------------------------------------
+ unconfined_u:unconfined_r:unconfined_t:s0
+(1 row)
+
+SET sepgsql.debug_audit = true;
+SET client_min_messages = LOG;
+CREATE DATABASE regtest_sepgsql_test_database;
+LOG:  SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template1"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database: regtest_sepgsql_test_database"
+--
+-- clean-up 
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 331bbd7..886f1fa 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -107,6 +107,91 @@ sepgsql_client_auth(Port *port, int status)
 }
 
 /*
+ * sepgsql_object_prep_create
+ *
+ * Entrypoint of OAT_PREP_CREATE event via object_access_hook.
+ */
+static void
+sepgsql_object_prep_create(Oid classId, Oid objectId, int subId,
+						   Datum argument)
+{
+	ObjectAccessCreateObjectArgs   *args
+		= (ObjectAccessCreateObjectArgs *)DatumGetPointer(argument);
+	sepgsql_creation_info		   *cinfo;
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (OAT_PREP_CREATE,
+									classId, objectId, subId,
+									argument);
+
+	/*
+	 * create sepgsql's private data to be delivered to
+	 * the post-creation hook.
+	 */
+	cinfo = palloc(sizeof(sepgsql_creation_info));
+	cinfo->classId = classId;
+	cinfo->private_next = *(args->common.private);
+
+	switch (classId)
+	{
+		case DatabaseRelationId:
+			cinfo->ncontext =
+				sepgsql_database_prep_create(args->pg_database.datname,
+											 args->pg_database.datsourceId,
+											 args->pg_database.tablespaceId);
+			break;
+
+		default:
+			/* Ignore unsupported object classes */
+			break;
+	}
+	*(args->common.private) = PointerGetDatum(cinfo);
+}
+
+/*
+ * sepgsql_object_post_create
+ *
+ * Entrypoint of OAT_POST_CREATE event via object_access_hook.
+ */
+static void
+sepgsql_object_post_create(Oid classId, Oid objectId, int subId,
+						   Datum argument)
+{
+	sepgsql_creation_info  *cinfo
+		= (sepgsql_creation_info *)DatumGetPointer(argument);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (OAT_POST_CREATE,
+									classId, objectId, subId,
+									!cinfo ? 0 : cinfo->private_next);
+	switch (classId)
+	{
+		case DatabaseRelationId:
+			sepgsql_database_post_create(objectId, cinfo->ncontext);
+			break;
+
+		case NamespaceRelationId:
+			sepgsql_schema_post_create(objectId);
+			break;
+
+		case RelationRelationId:
+			if (subId == 0)
+				sepgsql_relation_post_create(objectId);
+			else
+				sepgsql_attribute_post_create(objectId, subId);
+			break;
+
+		case ProcedureRelationId:
+			sepgsql_proc_post_create(objectId);
+			break;
+
+		default:
+			/* Ignore unsupported object classes */
+			break;
+	}
+}
+
+/*
  * sepgsql_object_access
  *
  * Entrypoint of the object_access_hook. This routine performs as
@@ -114,41 +199,17 @@ sepgsql_client_auth(Port *port, int status)
  */
 static void
 sepgsql_object_access(ObjectAccessType access,
-					  Oid classId,
-					  Oid objectId,
-					  int subId)
+					  Oid classId, Oid objectId, int subId,
+					  Datum argument)
 {
-	if (next_object_access_hook)
-		(*next_object_access_hook) (access, classId, objectId, subId);
-
 	switch (access)
 	{
+		case OAT_PREP_CREATE:
+			sepgsql_object_prep_create(classId, objectId, subId, argument);
+			break;
+
 		case OAT_POST_CREATE:
-			switch (classId)
-			{
-				case DatabaseRelationId:
-					sepgsql_database_post_create(objectId);
-					break;
-
-				case NamespaceRelationId:
-					sepgsql_schema_post_create(objectId);
-					break;
-
-				case RelationRelationId:
-					if (subId == 0)
-						sepgsql_relation_post_create(objectId);
-					else
-						sepgsql_attribute_post_create(objectId, subId);
-					break;
-
-				case ProcedureRelationId:
-					sepgsql_proc_post_create(objectId);
-					break;
-
-				default:
-					/* Ignore unsupported object classes */
-					break;
-			}
+			sepgsql_object_post_create(classId, objectId, subId, argument);
 			break;
 
 		default:
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index b4c1dfd..0d0eead 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -211,6 +211,24 @@
 #define SEPG_DB_VIEW__EXPAND				(1<<6)
 
 /*
+ * sepgsql_creation_info
+ *
+ * A data structure to store contextual information between prep-creation
+ * and post-creation hooks; mostly a security label to be assigned on the
+ * newly constructed object.
+ */
+typedef struct {
+	/* oid of the catalog that stores this new object */
+	Oid			classId;
+
+	/* private field for stacking module */
+	Datum		private_next;
+
+	/* a default security label to be assigned on */
+	const char *ncontext;
+} sepgsql_creation_info;
+
+/*
  * hooks.c
  */
 extern bool sepgsql_get_permissive(void);
@@ -286,7 +304,10 @@ extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort);
 /*
  * database.c
  */
-extern void sepgsql_database_post_create(Oid databaseId);
+extern const char *sepgsql_database_prep_create(const char *datname,
+												Oid datsourceId,
+												Oid tablespaceId);
+extern void sepgsql_database_post_create(Oid databaseId, const char *seclabel);
 extern void sepgsql_database_relabel(Oid databaseId, const char *seclabel);
 
 /*
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
new file mode 100644
index 0000000..e75d57b
--- /dev/null
+++ b/contrib/sepgsql/sql/create.sql
@@ -0,0 +1,15 @@
+--
+-- Regression Test for Creation of Object Permission Checks
+--
+
+-- confirm required permissions using audit messages
+-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0
+SET sepgsql.debug_audit = true;
+SET client_min_messages = LOG;
+
+CREATE DATABASE regtest_sepgsql_test_database;
+
+--
+-- clean-up 
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql
index 9b7262a..52237e6 100755
--- a/contrib/sepgsql/test_sepgsql
+++ b/contrib/sepgsql/test_sepgsql
@@ -259,6 +259,6 @@ echo "found ${NUM}"
 echo
 echo "============== running sepgsql regression tests       =============="
 
-make REGRESS="label dml misc" REGRESS_OPTS="--launcher ./launcher" installcheck
+make REGRESS="label dml create misc" REGRESS_OPTS="--launcher ./launcher" installcheck
 
 # exit with the exit code provided by "make"
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 4551db7..361a172 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -127,6 +127,7 @@ createdb(const CreatedbStmt *stmt)
 	int			dbconnlimit = -1;
 	int			notherbackends;
 	int			npreparedxacts;
+	Datum		hook_private = 0;
 	createdb_failure_params fparms;
 
 	/* Extract options from the statement node tree */
@@ -424,6 +425,10 @@ createdb(const CreatedbStmt *stmt)
 		/* Note there is no additional permission check in this path */
 	}
 
+	/* Prep-creation hook for new database */
+	InvokePrepCreateDatabaseHook(&hook_private, dbname, src_dboid,
+								 dst_deftablespace);
+
 	/*
 	 * Check for db name conflict.	This is just to give a more friendly error
 	 * message than "unique index violation".  There's a race condition but
@@ -515,7 +520,9 @@ createdb(const CreatedbStmt *stmt)
 	copyTemplateDependencies(src_dboid, dboid);
 
 	/* Post creation hook for new database */
-	InvokeObjectAccessHook(OAT_POST_CREATE, DatabaseRelationId, dboid, 0);
+	InvokeObjectAccessHookArg(OAT_POST_CREATE,
+							  DatabaseRelationId, dboid, 0,
+							  hook_private);
 
 	/*
 	 * Force a checkpoint before starting the copy. This will force dirty
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index 2925475..0531327 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -15,32 +15,91 @@
  * performing certain actions on a SQL object.	This is intended as
  * infrastructure for security or logging pluggins.
  *
+ * OAT_PREP_CREATE should be invoked around permission check of the core
+ * with arguments that describes about new object being constructed.
+ * Its private field can be used to deliver contextual information to
+ * post-creation hook.
+ *
  * OAT_POST_CREATE should be invoked just after the the object is created.
  * Typically, this is done after inserting the primary catalog records and
- * associated dependencies.
+ * associated dependencies. If prep-creation hook was invoked in same
+ * context, its private valud shall be provided to the modules.
  *
  * Other types may be added in the future.
  */
 typedef enum ObjectAccessType
 {
+	OAT_PREP_CREATE,
 	OAT_POST_CREATE,
 } ObjectAccessType;
 
 /*
- * Hook, and a macro to invoke it.
+ * ObjectAccessCreateObjectInfo
+ *
+ * It shall be used as argument of OAT_PREP_CREATE hook to inform about
+ * new object being constructed; prior to updates of system catalogs.
+ */
+typedef union
+{
+	struct {
+		Datum	   *private;	/* private data to deliver contextual
+								 * information into post creation hook */
+	} common;
+	struct {
+		Datum	   *private;	/* common */
+		const char *datname;	/* name of new database */
+		Oid			datsourceId;	/* oid of source database */
+		Oid			tablespaceId;	/* oid of default tablespace */
+	} pg_database;
+} ObjectAccessCreateObjectArgs;
+
+/*
+ * Hook, and macros to invoke it.
  */
 
 typedef void (*object_access_hook_type) (ObjectAccessType access,
-													 Oid classId,
-													 Oid objectId,
-													 int subId);
+										 Oid classId,
+										 Oid objectId,
+										 int subId,
+										 Datum argument);
 
 extern PGDLLIMPORT object_access_hook_type object_access_hook;
 
 #define InvokeObjectAccessHook(access,classId,objectId,subId)			\
 	do {																\
 		if (object_access_hook)											\
-			(*object_access_hook)((access),(classId),(objectId),(subId)); \
+			(*object_access_hook)((access),								\
+								  (classId),(objectId),(subId),			\
+								  (Datum) 0);							\
+	} while(0)
+
+#define InvokeObjectAccessHookArg(access,classId,objectId,subId,argument) \
+	do {																\
+		if (object_access_hook)											\
+			(*object_access_hook)((access),						\
+								  (classId),(objectId),(subId),			\
+								  (argument));							\
+	} while(0)
+
+/*
+ * OAT_PREP_CREATE specific macros, because it takes various arguments
+ * depending on object classes.
+ */
+#define InvokePrepCreateDatabaseHook(_private,_datname,_source,_tablespace) \
+	do {																\
+		if (object_access_hook)											\
+		{																\
+			ObjectAccessCreateObjectArgs	__args;						\
+																		\
+			__args.pg_database.private = (_private);					\
+			__args.pg_database.datname = (_datname);					\
+			__args.pg_database.datsourceId = (_source);					\
+			__args.pg_database.tablespaceId = (_tablespace);			\
+																		\
+			(*object_access_hook)(OAT_PREP_CREATE,						\
+								  DatabaseRelationId, InvalidOid, 0,	\
+								  PointerGetDatum(&__args));			\
+		}																\
 	} while(0)
 
 #endif   /* OBJECTACCESS_H */
pgsql-v9.2-prep-creation-hook-part-4.v1.procedure.patchapplication/octet-stream; name=pgsql-v9.2-prep-creation-hook-part-4.v1.procedure.patchDownload
diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index d450dc5..68d7e61 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -39,13 +39,25 @@ LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfine
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_seq"
 CREATE TYPE regtest_type AS (a int, b text);
 LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+CREATE FUNCTION regtest_func (text) RETURNS bool LANGUAGE plpgsql
+    AS 'BEGIN RAISE NOTICE ''hello => %'', $1; RETURN true; END';
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="function regtest_func(text)"
+CREATE AGGREGATE regtest_agg (
+    sfunc1 = int4pl, basetype = int4, stype1 = int4,
+    initcond1 = '0'
+);
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="function regtest_agg(integer)"
 --
 -- clean-up 
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
 DROP SCHEMA IF EXISTS regtest_schema CASCADE;
-NOTICE:  drop cascades to 4 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table regtest_table
 drop cascades to view regtest_view
 drop cascades to sequence regtest_seq
 drop cascades to type regtest_type
+drop cascades to function regtest_func(text)
+drop cascades to function regtest_agg(integer)
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index aa56d9c..1eeec1e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -155,6 +155,15 @@ sepgsql_object_prep_create(Oid classId, Oid objectId, int subId,
 										 args->pg_class.tupdesc);
 			break;
 
+		case ProcedureRelationId:
+			cinfo->ncontext =
+				sepgsql_proc_prep_create(args->pg_proc.proname,
+										 args->pg_proc.proargs,
+										 args->pg_proc.namespaceId,
+										 args->pg_proc.languageId,
+										 args->pg_proc.leakproof);
+			break;
+
 		default:
 			/* Ignore unsupported object classes */
 			break;
@@ -196,7 +205,8 @@ sepgsql_object_post_create(Oid classId, Oid objectId, int subId,
 			break;
 
 		case ProcedureRelationId:
-			sepgsql_proc_post_create(objectId);
+			sepgsql_proc_post_create(objectId,
+									 !cinfo ? NULL : cinfo->ncontext);
 			break;
 
 		default:
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index 9630d45..c0e2c0e 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -18,6 +18,7 @@
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
 #include "commands/seclabel.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/tqual.h"
@@ -25,55 +26,131 @@
 #include "sepgsql.h"
 
 /*
- * sepgsql_proc_post_create
+ * sepgsql_proc_prep_create
+ *
+ *
  *
- * This routine assigns a default security label on a newly defined
- * procedure.
  */
-void
-sepgsql_proc_post_create(Oid functionId)
+const char *
+sepgsql_proc_prep_create(const char *proname,
+						 oidvector *proargs,
+						 Oid namespaceId,
+						 Oid languageId,
+						 bool leakproof)
 {
-	Relation	rel;
-	ScanKeyData skey;
-	SysScanDesc sscan;
-	HeapTuple	tuple;
-	Oid			namespaceId;
-	ObjectAddress object;
 	char	   *scontext;
 	char	   *tcontext;
 	char	   *ncontext;
+	uint32		required;
+	StringInfoData	audit_name;
+	ObjectAddress	object;
+	int			i;
 
 	/*
-	 * Fetch namespace of the new procedure. Because pg_proc entry is not
-	 * visible right now, we need to scan the catalog using SnapshotSelf.
+	 * check db_schema:{add_name} permission
 	 */
-	rel = heap_open(ProcedureRelationId, AccessShareLock);
+	object.classId = NamespaceRelationId;
+	object.objectId = namespaceId;
+	object.objectSubId = 0;
+	sepgsql_avc_check_perms(&object,
+							SEPG_CLASS_DB_SCHEMA,
+							SEPG_DB_SCHEMA__ADD_NAME,
+							getObjectDescription(&object),
+							true);
 
-	ScanKeyInit(&skey,
-				ObjectIdAttributeNumber,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(functionId));
+	/*
+	 * compute default security label of new function
+	 */
+	scontext = sepgsql_get_client_label();
+	tcontext = sepgsql_get_label(NamespaceRelationId, namespaceId, 0);
+	ncontext = sepgsql_compute_create(scontext, tcontext,
+									  SEPG_CLASS_DB_PROCEDURE);
 
-	sscan = systable_beginscan(rel, ProcedureOidIndexId, true,
-							   SnapshotSelf, 1, &skey);
+	/*
+	 * check db_procedure:{create (install)} permission
+	 */
+	required = SEPG_DB_PROCEDURE__CREATE;
+	if (leakproof)
+		required |= SEPG_DB_PROCEDURE__INSTALL;
 
-	tuple = systable_getnext(sscan);
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "catalog lookup failed for proc %u", functionId);
+	/*
+	 * XXX - format_procedure() is not available at this point,
+	 * so we need to construct human readable function names by hand.
+	 */
+	initStringInfo(&audit_name);
+	appendStringInfo(&audit_name, _("function %s("),
+					 quote_identifier(proname));
+	for (i = 0; i < proargs->dim1; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&audit_name, ',');
+		appendStringInfoString(&audit_name,
+							   format_type_be(proargs->values[i]));
+	}
+	appendStringInfoChar(&audit_name, ')');
+
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_PROCEDURE,
+								  required,
+								  audit_name.data,
+								  true);
 
-	namespaceId = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace;
+	/*
+	 * XXX - db_language:{implement} also shoule be checked here.
+	 */
+	return ncontext;
+}
 
-	systable_endscan(sscan);
-	heap_close(rel, AccessShareLock);
+/*
+ * sepgsql_proc_post_create
+ *
+ * This routine assigns a default security label on a newly defined
+ * procedure.
+ */
+void
+sepgsql_proc_post_create(Oid functionId, const char *ncontext)
+{
+	ObjectAddress	object;
 
 	/*
-	 * Compute a default security label when we create a new procedure object
-	 * under the specified namespace.
+	 * In the case when system internal functions are constructed,
+	 * prep-creation hook is not invoked, security label to be
+	 * assigned on is not also computed yet. Thus, we try to obtain 
+	 * the default security label based on the properties of system
+	 * catalogs.
 	 */
-	scontext = sepgsql_get_client_label();
-	tcontext = sepgsql_get_label(NamespaceRelationId, namespaceId, 0);
-	ncontext = sepgsql_compute_create(scontext, tcontext,
-									  SEPG_CLASS_DB_PROCEDURE);
+	if (!ncontext)
+	{
+		Relation	rel;
+		ScanKeyData skey;
+		SysScanDesc sscan;
+		HeapTuple	tuple;
+		Form_pg_proc	procForm;
+
+		rel = heap_open(ProcedureRelationId, AccessShareLock);
+
+		ScanKeyInit(&skey,
+					ObjectIdAttributeNumber,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(functionId));
+
+		sscan = systable_beginscan(rel, ProcedureOidIndexId, true,
+								   SnapshotSelf, 1, &skey);
+
+		tuple = systable_getnext(sscan);
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "catalog lookup failed for proc %u", functionId);
+
+		procForm = (Form_pg_proc) GETSTRUCT(tuple);
+
+		ncontext = sepgsql_proc_prep_create(NameStr(procForm->proname),
+											&procForm->proargtypes,
+											procForm->pronamespace,
+											procForm->prolang,
+											false);
+		systable_endscan(sscan);
+		heap_close(rel, AccessShareLock);
+	}
 
 	/*
 	 * Assign the default security label on a new procedure
@@ -82,9 +159,6 @@ sepgsql_proc_post_create(Oid functionId)
 	object.objectId = functionId;
 	object.objectSubId = 0;
 	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
-
-	pfree(tcontext);
-	pfree(ncontext);
 }
 
 /*
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index cd9c4c6..56d2056 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -338,7 +338,12 @@ extern void sepgsql_relation_relabel(Oid relOid, const char *seclabel);
 /*
  * proc.c
  */
-extern void sepgsql_proc_post_create(Oid functionId);
+extern const char *sepgsql_proc_prep_create(const char *proname,
+											oidvector *proargs,
+											Oid namespaceId,
+											Oid languageId,
+											bool leakproof);
+extern void sepgsql_proc_post_create(Oid functionId, const char *ncontext);
 extern void sepgsql_proc_relabel(Oid functionId, const char *seclabel);
 
 #endif   /* SEPGSQL_H */
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index 4e12411..bc4a664 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -21,6 +21,14 @@ CREATE SEQUENCE regtest_seq;
 
 CREATE TYPE regtest_type AS (a int, b text);
 
+CREATE FUNCTION regtest_func (text) RETURNS bool LANGUAGE plpgsql
+    AS 'BEGIN RAISE NOTICE ''hello => %'', $1; RETURN true; END';
+
+CREATE AGGREGATE regtest_agg (
+    sfunc1 = int4pl, basetype = int4, stype1 = int4,
+    initcond1 = '0'
+);
+
 --
 -- clean-up 
 --
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 86e8c6b..0cd0ca3 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -50,7 +50,8 @@ AggregateCreate(const char *aggName,
 				List *aggfinalfnName,
 				List *aggsortopName,
 				Oid aggTransType,
-				const char *agginitval)
+				const char *agginitval,
+				Datum hook_private)
 {
 	Relation	aggdesc;
 	HeapTuple	tup;
@@ -229,7 +230,8 @@ AggregateCreate(const char *aggName,
 							  NIL,		/* parameterDefaults */
 							  PointerGetDatum(NULL),	/* proconfig */
 							  1,	/* procost */
-							  0);		/* prorows */
+							  0,	/* prorows */
+							  hook_private);	/* hook_private */
 
 	/*
 	 * Okay to create the pg_aggregate entry.
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 8378c36..7fb5ef2 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -85,7 +85,8 @@ ProcedureCreate(const char *procedureName,
 				List *parameterDefaults,
 				Datum proconfig,
 				float4 procost,
-				float4 prorows)
+				float4 prorows,
+				Datum hook_private)
 {
 	Oid			retval;
 	int			parameterCount;
@@ -646,7 +647,9 @@ ProcedureCreate(const char *procedureName,
 	heap_freetuple(tup);
 
 	/* Post creation hook for new function */
-	InvokeObjectAccessHook(OAT_POST_CREATE, ProcedureRelationId, retval, 0);
+	InvokeObjectAccessHookArg(OAT_POST_CREATE,
+							  ProcedureRelationId, retval, 0,
+							  hook_private);
 
 	heap_close(rel, RowExclusiveLock);
 
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index a2122c1..f02a467 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -25,7 +25,9 @@
 #include "access/heapam.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -60,6 +62,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters)
 	Oid		   *aggArgTypes;
 	int			numArgs;
 	Oid			transTypeId;
+	Datum		hook_private = 0;
 	ListCell   *pl;
 
 	/* Convert list of names to a name and namespace */
@@ -192,6 +195,11 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters)
 							format_type_be(transTypeId))));
 	}
 
+	/* Prep-creation hook for new aggregate functions */
+	InvokePrepCreateProcedureHook(&hook_private, aggName,
+								  buildoidvector(aggArgTypes, numArgs),
+								  aggNamespace, INTERNALlanguageId, false);
+
 	/*
 	 * Most of the argument-checking is done inside of AggregateCreate
 	 */
@@ -203,7 +211,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters)
 					finalfuncName,		/* final function name */
 					sortoperatorName,	/* sort operator name */
 					transTypeId,	/* transition data type */
-					initval);	/* initial condition */
+					initval,	/* initial condition */
+					hook_private);
 }
 
 
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 92abd44..33c65be 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -801,6 +801,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
 	HeapTuple	languageTuple;
 	Form_pg_language languageStruct;
 	List	   *as_clause;
+	Datum		hook_private = 0;
 
 	/* Convert list of names to a name and namespace */
 	namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname,
@@ -935,6 +936,10 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("ROWS is not applicable when function does not return a set")));
 
+	/* Prep-creation hook for new function */
+	InvokePrepCreateProcedureHook(&hook_private, funcname, parameterTypes,
+								  namespaceId, languageOid, false);
+
 	/*
 	 * And now that we have all the parameters, and know we're permitted to do
 	 * so, go ahead and create the function.
@@ -960,7 +965,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
 					parameterDefaults,
 					PointerGetDatum(proconfig),
 					procost,
-					prorows);
+					prorows,
+					hook_private);
 }
 
 
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 98770c5..1392dca 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -146,7 +146,8 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 										 NIL,
 										 PointerGetDatum(NULL),
 										 1,
-										 0);
+										 0,
+										 (Datum) 0);
 		}
 
 		/*
@@ -181,7 +182,8 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 											NIL,
 											PointerGetDatum(NULL),
 											1,
-											0);
+											0,
+											(Datum) 0);
 			}
 		}
 		else
@@ -219,7 +221,8 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 										 NIL,
 										 PointerGetDatum(NULL),
 										 1,
-										 0);
+										 0,
+										 (Datum) 0);
 			}
 		}
 		else
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index a1628bc..1354215 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1472,7 +1472,8 @@ makeRangeConstructor(char *name, Oid namespace, Oid rangeOid, Oid subtype)
 								  NIL,	/* parameterDefaults */
 								  PointerGetDatum(NULL),		/* proconfig */
 								  1.0,	/* procost */
-								  0.0); /* prorows */
+								  0.0, /* prorows */
+								  (Datum) 0);	/* hook_private */
 
 		/*
 		 * Make the constructor internally-dependent on the range type so that
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index d151ee5..db57e03 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -65,6 +65,14 @@ typedef union
 		Oid			tablespaceId;
 		TupleDesc	tupdesc;
 	} pg_class;
+	struct {
+		Datum	   *private;
+		const char *proname;
+		oidvector  *proargs;
+		Oid			namespaceId;
+		Oid			languageId;
+		bool		leakproof;
+	} pg_proc;
 } ObjectAccessCreateObjectArgs;
 
 /*
@@ -151,4 +159,24 @@ extern PGDLLIMPORT object_access_hook_type object_access_hook;
 		}																\
 	} while(0)
 
+#define InvokePrepCreateProcedureHook(_private,_proname,_proargs,		\
+									  _namespace,_language,_leakproof)	\
+	do {																\
+		if (object_access_hook)											\
+		{																\
+			ObjectAccessCreateObjectArgs	__args;						\
+																		\
+			__args.pg_proc.private = (_private);						\
+			__args.pg_proc.proname = (_proname);						\
+			__args.pg_proc.proargs = (_proargs);						\
+			__args.pg_proc.namespaceId = (_namespace);					\
+			__args.pg_proc.languageId = (_language);					\
+			__args.pg_proc.leakproof = (_leakproof);					\
+																		\
+			(*object_access_hook)(OAT_PREP_CREATE,						\
+								  ProcedureRelationId, InvalidOid, 0,	\
+								  PointerGetDatum(&__args));			\
+		}																\
+	} while(0)
+
 #endif   /* OBJECTACCESS_H */
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 26966d2..7b2d172 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -237,6 +237,7 @@ extern void AggregateCreate(const char *aggName,
 				List *aggfinalfnName,
 				List *aggsortopName,
 				Oid aggTransType,
-				const char *agginitval);
+				const char *agginitval,
+				Datum hook_private);
 
 #endif   /* PG_AGGREGATE_H */
diff --git a/src/include/catalog/pg_proc_fn.h b/src/include/catalog/pg_proc_fn.h
index 09d779f..a28786f 100644
--- a/src/include/catalog/pg_proc_fn.h
+++ b/src/include/catalog/pg_proc_fn.h
@@ -37,7 +37,8 @@ extern Oid ProcedureCreate(const char *procedureName,
 				List *parameterDefaults,
 				Datum proconfig,
 				float4 procost,
-				float4 prorows);
+				float4 prorows,
+				Datum hook_private);
 
 extern bool function_parse_error_transpose(const char *prosrc);
 
pgsql-v9.2-prep-creation-hook-part-3.v1.relation.patchapplication/octet-stream; name=pgsql-v9.2-prep-creation-hook-part-3.v1.relation.patchDownload
diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index 3293a73..d450dc5 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -16,8 +16,36 @@ LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_
 CREATE SCHEMA regtest_schema;
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
 SET search_path = regtest_schema, public;
+CREATE TABLE regtest_table (x serial primary key, y text);
+NOTICE:  CREATE TABLE will create implicit sequence "regtest_table_x_seq" for serial column "regtest_table.x"
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_table_x_seq"
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="table regtest_table"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column tableoid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmax"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmax"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmin"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmin"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column ctid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column x"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column y"
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "regtest_table_pkey" for table "regtest_table"
+CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 10;
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="view regtest_view"
+CREATE SEQUENCE regtest_seq;
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_seq"
+CREATE TYPE regtest_type AS (a int, b text);
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
 --
 -- clean-up 
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
 DROP SCHEMA IF EXISTS regtest_schema CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table regtest_table
+drop cascades to view regtest_view
+drop cascades to sequence regtest_seq
+drop cascades to type regtest_type
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 2b62a75..aa56d9c 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -146,6 +146,15 @@ sepgsql_object_prep_create(Oid classId, Oid objectId, int subId,
 				sepgsql_schema_prep_create(args->pg_namespace.nspname);
 			break;
 
+		case RelationRelationId:
+			sepgsql_relation_prep_create(cinfo,
+										 args->pg_class.relname,
+										 args->pg_class.relkind,
+										 args->pg_class.namespaceId,
+										 args->pg_class.tablespaceId,
+										 args->pg_class.tupdesc);
+			break;
+
 		default:
 			/* Ignore unsupported object classes */
 			break;
@@ -181,7 +190,7 @@ sepgsql_object_post_create(Oid classId, Oid objectId, int subId,
 
 		case RelationRelationId:
 			if (subId == 0)
-				sepgsql_relation_post_create(objectId);
+				sepgsql_relation_post_create(cinfo, objectId);
 			else
 				sepgsql_attribute_post_create(objectId, subId);
 			break;
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 0767382..09f80d7 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -13,6 +13,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/sysattr.h"
+#include "catalog/heap.h"
 #include "catalog/indexing.h"
 #include "catalog/dependency.h"
 #include "catalog/pg_attribute.h"
@@ -113,102 +114,174 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 }
 
 /*
- * sepgsql_relation_post_create
+ * sepgsql_relation_prep_create
  *
- * The post creation hook of relation/attribute
+ * This routine computes default security label of relation and columns
+ * if relation is a table, then checks permissions to create them.
  */
 void
-sepgsql_relation_post_create(Oid relOid)
+sepgsql_relation_prep_create(sepgsql_creation_info *info,
+							 const char *relname, char relkind,
+							 Oid namespaceId, Oid tablespaceId,
+							 TupleDesc tupdesc)
 {
-	Relation	rel;
-	ScanKeyData skey;
-	SysScanDesc sscan;
-	HeapTuple	tuple;
-	Form_pg_class classForm;
-	ObjectAddress object;
+	char	   *scontext;
+	char	   *tcontext;
+	char	   *ncontext;
+	char	   *acontext;
+	char		audit_name[2 * NAMEDATALEN + 16];
 	uint16		tclass;
-	char	   *scontext;		/* subject */
-	char	   *tcontext;		/* schema */
-	char	   *rcontext;		/* relation */
-	char	   *ccontext;		/* column */
+	ObjectAddress	object;
 
 	/*
-	 * Fetch catalog record of the new relation. Because pg_class entry is not
-	 * visible right now, we need to scan the catalog using SnapshotSelf.
+	 * so check db_schema:{add_name} permission
 	 */
-	rel = heap_open(RelationRelationId, AccessShareLock);
-
-	ScanKeyInit(&skey,
-				ObjectIdAttributeNumber,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(relOid));
-
-	sscan = systable_beginscan(rel, ClassOidIndexId, true,
-							   SnapshotSelf, 1, &skey);
+	object.classId = NamespaceRelationId;
+	object.objectId = namespaceId;
+	object.objectSubId = 0;
+	sepgsql_avc_check_perms(&object,
+							SEPG_CLASS_DB_SCHEMA,
+							SEPG_DB_SCHEMA__ADD_NAME,
+							getObjectDescription(&object),
+							true);
+	/*
+	 * compute default security label of new relation
+	 */
+	switch (relkind)
+	{
+		case RELKIND_RELATION:
+			tclass = SEPG_CLASS_DB_TABLE;
+			snprintf(audit_name, sizeof(audit_name), "table %s", relname);
+			break;
 
-	tuple = systable_getnext(sscan);
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "catalog lookup failed for relation %u", relOid);
+		case RELKIND_SEQUENCE:
+			tclass = SEPG_CLASS_DB_SEQUENCE;
+			snprintf(audit_name, sizeof(audit_name), "sequence %s", relname);
+			break;
 
-	classForm = (Form_pg_class) GETSTRUCT(tuple);
+		case RELKIND_VIEW:
+			tclass = SEPG_CLASS_DB_VIEW;
+			snprintf(audit_name, sizeof(audit_name), "view %s", relname);
+			break;
 
-	if (classForm->relkind == RELKIND_RELATION)
-		tclass = SEPG_CLASS_DB_TABLE;
-	else if (classForm->relkind == RELKIND_SEQUENCE)
-		tclass = SEPG_CLASS_DB_SEQUENCE;
-	else if (classForm->relkind == RELKIND_VIEW)
-		tclass = SEPG_CLASS_DB_VIEW;
-	else
-		goto out;				/* No need to assign individual labels */
-
-	/*
-	 * Compute a default security label when we create a new relation object
-	 * under the specified namespace.
-	 */
+		default:
+			/*
+			 * No need to check permission any more, if relation is not
+			 * a table, sequence or view.
+			 */
+			info->ncontext = NULL;
+			info->nattrs = 0;
+			info->acontexts = NULL;
+			return;
+	}
 	scontext = sepgsql_get_client_label();
-	tcontext = sepgsql_get_label(NamespaceRelationId,
-								 classForm->relnamespace, 0);
-	rcontext = sepgsql_compute_create(scontext, tcontext, tclass);
+	tcontext = sepgsql_get_label(NamespaceRelationId, namespaceId, 0);
+	ncontext = sepgsql_compute_create(scontext, tcontext, tclass);
+	info->ncontext = ncontext;
 
 	/*
-	 * Assign the default security label on the new relation
+	 * check db_xxx:{create} permission
 	 */
-	object.classId = RelationRelationId;
-	object.objectId = relOid;
-	object.objectSubId = 0;
-	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext);
-
+	sepgsql_avc_check_perms_label(ncontext,
+								  tclass,
+								  SEPG_DB_TABLE__CREATE,
+								  audit_name,
+								  true);
 	/*
-	 * We also assigns a default security label on columns of the new regular
-	 * tables.
+	 * compute default security label of columns, and check
+	 * db_column:{create} permission, if relkind == RELKIND_RELATION
 	 */
-	if (classForm->relkind == RELKIND_RELATION)
+	if (relkind == RELKIND_RELATION)
 	{
-		AttrNumber	index;
+		AttrNumber	attnum;
+		int			shift = - FirstLowInvalidHeapAttributeNumber - 1;
 
-		ccontext = sepgsql_compute_create(scontext, rcontext,
+		/*
+		 * XXX - upcoming libselinux allows case handling when a new
+		 * object that has a particular name, but it is not supported
+		 * right now. So, we omit to compute default security label
+		 * for each columns, because it shall be uniform when same
+		 * client create columns under the same table.
+		 */
+		acontext = sepgsql_compute_create(scontext, ncontext,
 										  SEPG_CLASS_DB_COLUMN);
-		for (index = FirstLowInvalidHeapAttributeNumber + 1;
-			 index <= classForm->relnatts;
-			 index++)
+
+		info->nattrs = tupdesc->natts;
+		info->acontexts = palloc0(sizeof(const char *) *
+								  (tupdesc->natts + shift + 1));
+
+		for (attnum = FirstLowInvalidHeapAttributeNumber + 1;
+			 attnum <= tupdesc->natts;
+			 attnum++)
 		{
-			if (index == InvalidAttrNumber)
-				continue;
+			Form_pg_attribute	attr;
 
-			if (index == ObjectIdAttributeNumber && !classForm->relhasoids)
+			if (attnum == InvalidAttrNumber ||
+				(attnum == ObjectIdAttributeNumber && !tupdesc->tdhasoid))
+			{
+				info->acontexts[attnum + shift] = NULL;
 				continue;
+			}
+			/* check db_column:{create} permission */
+			if (attnum < 0)
+				attr = SystemAttributeDefinition(attnum, true);
+			else
+				attr = tupdesc->attrs[attnum - 1];
+			snprintf(audit_name, sizeof(audit_name), "table %s column %s",
+					 relname, NameStr(attr->attname));
 
-			object.classId = RelationRelationId;
-			object.objectId = relOid;
-			object.objectSubId = index;
-			SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext);
+			sepgsql_avc_check_perms_label(acontext,
+										  SEPG_CLASS_DB_COLUMN,
+										  SEPG_DB_COLUMN__CREATE,
+										  audit_name,
+										  true);
+			info->acontexts[attnum + shift] = acontext;
 		}
-		pfree(ccontext);
 	}
-	pfree(rcontext);
-out:
-	systable_endscan(sscan);
-	heap_close(rel, AccessShareLock);
+	else
+	{
+		info->nattrs = 0;
+		info->acontexts = NULL;
+	}
+}
+
+/*
+ * sepgsql_relation_post_create
+ *
+ * The post creation hook of relation/attribute
+ */
+void
+sepgsql_relation_post_create(sepgsql_creation_info *info,
+							 Oid relationOid)
+{
+	ObjectAddress	object;
+	AttrNumber	attnum;
+	int			shift = - FirstLowInvalidHeapAttributeNumber - 1;
+
+	if (!info || !info->ncontext)
+		return;
+
+	object.classId = RelationRelationId;
+	object.objectId = relationOid;
+	object.objectSubId = 0;
+	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, info->ncontext);
+
+	if (!info->acontexts)
+		return;
+
+	for (attnum = FirstLowInvalidHeapAttributeNumber + 1;
+		 attnum <= info->nattrs;
+		 attnum++)
+	{
+		if (!info->acontexts[attnum + shift])
+			continue;
+
+		object.classId = RelationRelationId;
+		object.objectId = relationOid;
+		object.objectSubId = attnum;
+		SetSecurityLabel(&object, SEPGSQL_LABEL_TAG,
+						 info->acontexts[attnum + shift]);
+	}
 }
 
 /*
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index b4a3e4d..cd9c4c6 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -226,6 +226,10 @@ typedef struct {
 
 	/* a default security label to be assigned on */
 	const char *ncontext;
+
+	/* a default security label of columns (only for tables) */
+	AttrNumber	nattrs;
+	const char  **acontexts;
 } sepgsql_creation_info;
 
 /*
@@ -323,7 +327,12 @@ extern void sepgsql_schema_relabel(Oid namespaceId, const char *seclabel);
 extern void sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum);
 extern void sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 						  const char *seclabel);
-extern void sepgsql_relation_post_create(Oid relOid);
+extern void sepgsql_relation_prep_create(sepgsql_creation_info *info,
+										 const char *relname, char relkind,
+										 Oid namespaceId, Oid tablespaceId,
+										 TupleDesc tupdesc);
+extern void sepgsql_relation_post_create(sepgsql_creation_info *info,
+										 Oid relationOid);
 extern void sepgsql_relation_relabel(Oid relOid, const char *seclabel);
 
 /*
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index 9ce1db9..4e12411 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -13,6 +13,14 @@ CREATE SCHEMA regtest_schema;
 
 SET search_path = regtest_schema, public;
 
+CREATE TABLE regtest_table (x serial primary key, y text);
+
+CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 10;
+
+CREATE SEQUENCE regtest_seq;
+
+CREATE TYPE regtest_type AS (a int, b text);
+
 --
 -- clean-up 
 --
diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql
index 52237e6..4e43e97 100755
--- a/contrib/sepgsql/test_sepgsql
+++ b/contrib/sepgsql/test_sepgsql
@@ -163,6 +163,34 @@ if [ "${POLICY_STATUS}" != on ]; then
     exit 1
 fi
 
+# Verify that sepgsql_enable_users_ddl is active.
+echo -n "checking whether DDL is allowed     ... "
+POLICY_STATUS=`getsebool sepgsql_enable_users_ddl | awk '{print $3}'`
+echo ${POLICY_STATUS:-failed}
+if [ "${POLICY_STATUS}" != on ]; then
+    echo ""
+    echo "The SELinux boolean 'sepgsql_enable_users_ddl' must be"
+    echo "turned on in order to enable the rules necessary to run the"
+    echo "regression tests."
+    echo ""
+    if [ "${POLICY_STATUS}" = "" ]; then
+        echo "We attempted to determine the state of this Boolean using"
+        echo "'getsebool', but that command did not produce the expected"
+        echo "output.  Please verify that getsebool is available and in"
+        echo "your PATH."
+    else
+        echo "You can turn on this variable using the following commands:"
+        echo ""
+        echo "  \$ sudo setsebool sepgsql_enable_users_ddl on"
+        echo ""
+        echo "For security reasons, it is suggested that you turn off this"
+        echo "variable unless you want unprivileged users to execute"
+	echo "DDL commands."
+    fi
+    echo ""
+    exit 1
+fi
+
 # 'psql' command must be executable from test domain
 echo -n "checking whether we can run psql    ... "
 CMD_PSQL="${PG_BINDIR}/psql"
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index d0a0e92..4ca33ab 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,7 +248,8 @@ Boot_CreateStmt:
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
 													  false,
-													  true);
+													  true,
+													  (Datum) 0);
 						elog(DEBUG4, "relation created with OID %u", id);
 					}
 					do_end();
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e11d896..84613f7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -993,7 +993,8 @@ heap_create_with_catalog(const char *relname,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
 						 bool use_user_acl,
-						 bool allow_system_table_mods)
+						 bool allow_system_table_mods,
+						 Datum hook_private)
 {
 	Relation	pg_class_desc;
 	Relation	new_rel_desc;
@@ -1279,7 +1280,9 @@ heap_create_with_catalog(const char *relname,
 	}
 
 	/* Post creation hook for new relation */
-	InvokeObjectAccessHook(OAT_POST_CREATE, RelationRelationId, relid, 0);
+	InvokeObjectAccessHookArg(OAT_POST_CREATE,
+							  RelationRelationId, relid, 0,
+							  hook_private);
 
 	/*
 	 * Store any supplied constraints and defaults.
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 3a40e8b..a28c423 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -228,7 +228,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio
 										   ONCOMMIT_NOOP,
 										   reloptions,
 										   false,
-										   true);
+										   true,
+										   (Datum) 0);
 	Assert(toast_relid != InvalidOid);
 
 	/* make the toast relation visible, else heap_open will fail */
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index edec44d..08ad2bc 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -645,7 +645,8 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace)
 										  ONCOMMIT_NOOP,
 										  reloptions,
 										  false,
-										  true);
+										  true,
+										  (Datum) 0);
 	Assert(OIDNewHeap != InvalidOid);
 
 	ReleaseSysCache(tuple);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c4622c0..a8b5a56 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -410,6 +410,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		hook_private = 0;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -575,6 +576,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 		}
 	}
 
+	/* Prep-creation hook for new relation */
+	InvokePrepCreateRelationHook(&hook_private, relname, relkind,
+								 namespaceId, tablespaceId, descriptor);
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -599,7 +604,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 										  stmt->oncommit,
 										  reloptions,
 										  true,
-										  allowSystemTableMods);
+										  allowSystemTableMods,
+										  hook_private);
 
 	/* Store inheritance information for new rel. */
 	StoreCatalogInheritance(relationId, inheritOids);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index fd7a9ed..8d97d1e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,6 +43,7 @@
 #include "access/xact.h"
 #include "catalog/heap.h"
 #include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
 #include "catalog/toasting.h"
 #include "commands/tablespace.h"
 #include "commands/trigger.h"
@@ -2393,6 +2394,7 @@ OpenIntoRel(QueryDesc *queryDesc)
 	Oid			namespaceId;
 	Oid			tablespaceId;
 	Datum		reloptions;
+	Datum		hook_private = 0;
 	Oid			intoRelationId;
 	DR_intorel *myState;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -2458,6 +2460,12 @@ OpenIntoRel(QueryDesc *queryDesc)
 						   get_tablespace_name(tablespaceId));
 	}
 
+	/* Prep-creation hook for new relation */
+	InvokePrepCreateRelationHook(&hook_private, intoName,
+								 RELKIND_RELATION,
+								 namespaceId, tablespaceId,
+								 queryDesc->tupDesc);
+
 	/* Parse and validate any reloptions */
 	reloptions = transformRelOptions((Datum) 0,
 									 into->options,
@@ -2486,7 +2494,8 @@ OpenIntoRel(QueryDesc *queryDesc)
 											  into->onCommit,
 											  reloptions,
 											  true,
-											  allowSystemTableMods);
+											  allowSystemTableMods,
+											  hook_private);
 	Assert(intoRelationId != InvalidOid);
 
 	/*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index aee2d88..ca4d6a1 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -65,7 +65,8 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
 						 bool use_user_acl,
-						 bool allow_system_table_mods);
+						 bool allow_system_table_mods,
+						 Datum hook_private);
 
 extern void heap_drop_with_catalog(Oid relid);
 
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index cb5c396..d151ee5 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -10,6 +10,8 @@
 #ifndef OBJECTACCESS_H
 #define OBJECTACCESS_H
 
+#include "access/tupdesc.h"
+
 /*
  * Object access hooks are intended to be called just before or just after
  * performing certain actions on a SQL object.	This is intended as
@@ -55,6 +57,14 @@ typedef union
 		Datum	   *private;	/* common */
 		const char *nspname;	/* name of new schema */
 	} pg_namespace;
+	struct {
+		Datum	   *private;	/* common */
+		const char *relname;	/* name of new relation */
+		char		relkind;
+		Oid			namespaceId;
+		Oid			tablespaceId;
+		TupleDesc	tupdesc;
+	} pg_class;
 } ObjectAccessCreateObjectArgs;
 
 /*
@@ -121,4 +131,24 @@ extern PGDLLIMPORT object_access_hook_type object_access_hook;
 		}																\
 	} while(0)
 
+#define InvokePrepCreateRelationHook(_private,_relname,_relkind,		\
+									 _namespace,_tablespace,_tupdesc)	\
+	do {																\
+		if (object_access_hook)											\
+		{																\
+			ObjectAccessCreateObjectArgs	__args;						\
+																		\
+			__args.pg_class.private = (_private);						\
+			__args.pg_class.relname = (_relname);						\
+			__args.pg_class.relkind = (_relkind);						\
+			__args.pg_class.namespaceId = (_namespace);					\
+			__args.pg_class.tablespaceId = (_tablespace);				\
+			__args.pg_class.tupdesc = (_tupdesc);						\
+																		\
+			(*object_access_hook)(OAT_PREP_CREATE,						\
+								  RelationRelationId, InvalidOid, 0,	\
+								  PointerGetDatum(&__args));			\
+		}																\
+	} while(0)
+
 #endif   /* OBJECTACCESS_H */
pgsql-v9.2-prep-creation-hook-part-2.v1.schema.patchapplication/octet-stream; name=pgsql-v9.2-prep-creation-hook-part-2.v1.schema.patchDownload
diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index 95fc4f1..3293a73 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -13,7 +13,11 @@ SET client_min_messages = LOG;
 CREATE DATABASE regtest_sepgsql_test_database;
 LOG:  SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template1"
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database: regtest_sepgsql_test_database"
+CREATE SCHEMA regtest_schema;
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+SET search_path = regtest_schema, public;
 --
 -- clean-up 
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
+DROP SCHEMA IF EXISTS regtest_schema CASCADE;
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 886f1fa..2b62a75 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -141,6 +141,11 @@ sepgsql_object_prep_create(Oid classId, Oid objectId, int subId,
 											 args->pg_database.tablespaceId);
 			break;
 
+		case NamespaceRelationId:
+			cinfo->ncontext =
+				sepgsql_schema_prep_create(args->pg_namespace.nspname);
+			break;
+
 		default:
 			/* Ignore unsupported object classes */
 			break;
@@ -171,7 +176,7 @@ sepgsql_object_post_create(Oid classId, Oid objectId, int subId,
 			break;
 
 		case NamespaceRelationId:
-			sepgsql_schema_post_create(objectId);
+			sepgsql_schema_post_create(objectId, cinfo->ncontext);
 			break;
 
 		case RelationRelationId:
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index a167be1..07dbf80 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -20,38 +20,61 @@
 #include "sepgsql.h"
 
 /*
- * sepgsql_schema_post_create
+ * sepgsql_schema_prep_create
  *
- * This routine assigns a default security label on a newly defined
- * schema.
+ * It computes a default security label of schema object, and checks
+ * permission to create a new schema object.
  */
-void
-sepgsql_schema_post_create(Oid namespaceId)
+const char *
+sepgsql_schema_prep_create(const char *nspname)
 {
-	char	   *scontext;
+	char	   *scontext = sepgsql_get_client_label();
 	char	   *tcontext;
 	char	   *ncontext;
-	ObjectAddress object;
+	char		audit_name[NAMEDATALEN + 16];
 
 	/*
 	 * Compute a default security label when we create a new schema object
 	 * under the working database.
 	 */
-	scontext = sepgsql_get_client_label();
 	tcontext = sepgsql_get_label(DatabaseRelationId, MyDatabaseId, 0);
 	ncontext = sepgsql_compute_create(scontext, tcontext,
 									  SEPG_CLASS_DB_SCHEMA);
 
 	/*
-	 * Assign the default security label on a new procedure
+	 * Check db_schema:{create} permission
 	 */
+	if (strncmp(nspname, "pg_temp_", 8) == 0)
+		snprintf(audit_name, sizeof(audit_name), "schema pg_temp");
+	else
+		snprintf(audit_name, sizeof(audit_name), "schema %s", nspname);
+
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_SCHEMA,
+								  SEPG_DB_SCHEMA__CREATE,
+								  audit_name,
+								  true);
+
+	pfree(tcontext);
+
+	return ncontext;
+}
+
+
+/*
+ * sepgsql_schema_post_create
+ *
+ * This routine assigns a default security label on a newly defined schema.
+ */
+void
+sepgsql_schema_post_create(Oid namespaceId, const char *seclabel)
+{
+	ObjectAddress	object;
+
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
-
-	pfree(ncontext);
-	pfree(tcontext);
+	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, seclabel);
 }
 
 /*
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index 0d0eead..b4a3e4d 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -313,7 +313,8 @@ extern void sepgsql_database_relabel(Oid databaseId, const char *seclabel);
 /*
  * schema.c
  */
-extern void sepgsql_schema_post_create(Oid namespaceId);
+extern const char *sepgsql_schema_prep_create(const char *nspname);
+extern void sepgsql_schema_post_create(Oid namespaceId, const char *seclabel);
 extern void sepgsql_schema_relabel(Oid namespaceId, const char *seclabel);
 
 /*
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index e75d57b..9ce1db9 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -9,7 +9,13 @@ SET client_min_messages = LOG;
 
 CREATE DATABASE regtest_sepgsql_test_database;
 
+CREATE SCHEMA regtest_schema;
+
+SET search_path = regtest_schema, public;
+
 --
 -- clean-up 
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
+
+DROP SCHEMA IF EXISTS regtest_schema CASCADE;
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 6d4d4b1..abaa008 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -21,6 +21,7 @@
 
 #include "access/xact.h"
 #include "catalog/dependency.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_conversion.h"
@@ -3393,9 +3394,12 @@ InitTempTableNamespace(void)
 	char		namespaceName[NAMEDATALEN];
 	Oid			namespaceId;
 	Oid			toastspaceId;
+	Datum		hook_private = 0;
 
 	Assert(!OidIsValid(myTempNamespace));
 
+	snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId);
+
 	/*
 	 * First, do permission check to see if we are authorized to make temp
 	 * tables.	We use a nonstandard error message here since "databasename:
@@ -3413,6 +3417,9 @@ InitTempTableNamespace(void)
 				 errmsg("permission denied to create temporary tables in database \"%s\"",
 						get_database_name(MyDatabaseId))));
 
+	/* Prep-creation hook for new temp-schema */
+	InvokePrepCreateSchemaHook(&hook_private, namespaceName);
+
 	/*
 	 * Do not allow a Hot Standby slave session to make temp tables.  Aside
 	 * from problems with modifying the system catalogs, there is a naming
@@ -3428,8 +3435,6 @@ InitTempTableNamespace(void)
 				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
 				 errmsg("cannot create temporary tables during recovery")));
 
-	snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId);
-
 	namespaceId = get_namespace_oid(namespaceName, true);
 	if (!OidIsValid(namespaceId))
 	{
@@ -3441,7 +3446,9 @@ InitTempTableNamespace(void)
 		 * temp tables.  This works because the places that access the temp
 		 * namespace for my own backend skip permissions checks on it.
 		 */
-		namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID);
+		namespaceId = NamespaceCreate(namespaceName,
+									  BOOTSTRAP_SUPERUSERID,
+									  hook_private);
 		/* Advance command counter to make namespace visible */
 		CommandCounterIncrement();
 	}
@@ -3465,7 +3472,9 @@ InitTempTableNamespace(void)
 	toastspaceId = get_namespace_oid(namespaceName, true);
 	if (!OidIsValid(toastspaceId))
 	{
-		toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID);
+		toastspaceId = NamespaceCreate(namespaceName,
+									   BOOTSTRAP_SUPERUSERID,
+									   hook_private);
 		/* Advance command counter to make namespace visible */
 		CommandCounterIncrement();
 	}
diff --git a/src/backend/catalog/pg_namespace.c b/src/backend/catalog/pg_namespace.c
index ceebac2..48b9fc9 100644
--- a/src/backend/catalog/pg_namespace.c
+++ b/src/backend/catalog/pg_namespace.c
@@ -29,7 +29,7 @@
  * ---------------
  */
 Oid
-NamespaceCreate(const char *nspName, Oid ownerId)
+NamespaceCreate(const char *nspName, Oid ownerId, Datum hook_private)
 {
 	Relation	nspdesc;
 	HeapTuple	tup;
@@ -86,7 +86,8 @@ NamespaceCreate(const char *nspName, Oid ownerId)
 	recordDependencyOnCurrentExtension(&myself, false);
 
 	/* Post creation hook for new schema */
-	InvokeObjectAccessHook(OAT_POST_CREATE, NamespaceRelationId, nspoid, 0);
-
+	InvokeObjectAccessHookArg(OAT_POST_CREATE,
+							  NamespaceRelationId, nspoid, 0,
+							  hook_private);
 	return nspoid;
 }
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 8daa9d0..d6f1927 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -19,6 +19,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
 #include "commands/schemacmds.h"
@@ -48,6 +49,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	Oid			owner_uid;
 	Oid			saved_uid;
 	int			save_sec_context;
+	Datum		hook_private = 0;
 	AclResult	aclresult;
 
 	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
@@ -74,6 +76,9 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 
 	check_is_member_of_role(saved_uid, owner_uid);
 
+	/* Prep-creation hook for new schema */
+	InvokePrepCreateSchemaHook(&hook_private, schemaName);
+
 	/* Additional check to protect reserved schema names */
 	if (!allowSystemTableMods && IsReservedName(schemaName))
 		ereport(ERROR,
@@ -94,7 +99,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 							save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
 	/* Create the schema's namespace */
-	namespaceId = NamespaceCreate(schemaName, owner_uid);
+	namespaceId = NamespaceCreate(schemaName, owner_uid, hook_private);
 
 	/* Advance cmd counter to make the namespace visible */
 	CommandCounterIncrement();
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index 0531327..cb5c396 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -51,6 +51,10 @@ typedef union
 		Oid			datsourceId;	/* oid of source database */
 		Oid			tablespaceId;	/* oid of default tablespace */
 	} pg_database;
+	struct {
+		Datum	   *private;	/* common */
+		const char *nspname;	/* name of new schema */
+	} pg_namespace;
 } ObjectAccessCreateObjectArgs;
 
 /*
@@ -102,4 +106,19 @@ extern PGDLLIMPORT object_access_hook_type object_access_hook;
 		}																\
 	} while(0)
 
+#define InvokePrepCreateSchemaHook(_private,_nspname)					\
+	do {																\
+		if (object_access_hook)											\
+		{																\
+			ObjectAccessCreateObjectArgs	__args;						\
+																		\
+			__args.pg_namespace.private = (_private);					\
+			__args.pg_namespace.nspname = (_nspname);					\
+																		\
+			(*object_access_hook)(OAT_PREP_CREATE,						\
+								  NamespaceRelationId, InvalidOid, 0,	\
+								  PointerGetDatum(&__args));			\
+		}																\
+	} while(0)
+
 #endif   /* OBJECTACCESS_H */
diff --git a/src/include/catalog/pg_namespace.h b/src/include/catalog/pg_namespace.h
index 680802d7..eedcf1b 100644
--- a/src/include/catalog/pg_namespace.h
+++ b/src/include/catalog/pg_namespace.h
@@ -77,6 +77,7 @@ DESCR("standard public schema");
 /*
  * prototypes for functions in pg_namespace.c
  */
-extern Oid	NamespaceCreate(const char *nspName, Oid ownerId);
+extern Oid	NamespaceCreate(const char *nspName, Oid ownerId,
+							Datum hook_private);
 
 #endif   /* PG_NAMESPACE_H */
#2Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Kohei KaiGai (#1)
Re: Prep object creation hooks, and related sepgsql updates

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

We still don't have clear direction of the way to implement external permission
checks on object creation time. So, please consider these patches are on the
proof-of-concept stage; using prep-creation-hook to permission checks.

I wonder if you could implement that as an extension given the command
trigger patch finds its way in. What do you think?

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#3Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Dimitri Fontaine (#2)
Re: Prep object creation hooks, and related sepgsql updates

2011/11/26 Dimitri Fontaine <dimitri@2ndquadrant.fr>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

We still don't have clear direction of the way to implement external permission
checks on object creation time. So, please consider these patches are on the
proof-of-concept stage; using prep-creation-hook to permission checks.

I wonder if you could implement that as an extension given the command
trigger patch finds its way in.  What do you think?

Unfortunately, it does not solve my point.

My proposition allows an extension to deliver an opaque value being set up
at the prep-creation hook into post-creation hook. It shall be used to deliver
a security label to be assigned on the new object, however, it is unavailable
to assign on prep-creation phase, because its object-id is not fixed yet.
(It is not an option to ask operating system a default security label of the
new object twice, because security policy may be reloaded between prep-
and post-.)

It is also reason why I mentioned about an idea that put prep-creation hook
on a limited number of object classes only. It requires us code modification
to maintain an opaque private between prep- and post- hooks.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#4Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Kohei KaiGai (#3)
Re: Prep object creation hooks, and related sepgsql updates

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

I wonder if you could implement that as an extension given the command
trigger patch finds its way in.  What do you think?

Unfortunately, it does not solve my point.

[...]

It is also reason why I mentioned about an idea that put prep-creation hook
on a limited number of object classes only. It requires us code modification
to maintain an opaque private between prep- and post- hooks.

In my current proposal for command triggers, the trigger procedure is
given schemaname and objectname as separate arguments. It seems to me
easy enough to use that as a key to some data structure where the value
is any opaque data you need, and that you maintain in your extension
triggers code. You can write them in C.

I don't think schemaname+objectname fails to be unique, so I don't think
you need another kind of Oid in BEFORE creation triggers here.

--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#5Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Dimitri Fontaine (#4)
Re: Prep object creation hooks, and related sepgsql updates

2011/11/27 Dimitri Fontaine <dimitri@2ndquadrant.fr>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

I wonder if you could implement that as an extension given the command
trigger patch finds its way in.  What do you think?

Unfortunately, it does not solve my point.

[...]

It is also reason why I mentioned about an idea that put prep-creation hook
on a limited number of object classes only. It requires us code modification
to maintain an opaque private between prep- and post- hooks.

In my current proposal for command triggers, the trigger procedure is
given schemaname and objectname as separate arguments.  It seems to me
easy enough to use that as a key to some data structure where the value
is any opaque data you need, and that you maintain in your extension
triggers code. You can write them in C.

Sorry, it does not cover all the code paths that I want to apply permission
checks around creation of new tables.

The existing DAC checks permission on creation of new tables at
DefineRelation() and OpenIntoRel(), and sepgsql also wants to follow
this manner.
However, OpenIntoRel() does not go through ProcessUtility, so it seems
to me the command trigger is not invoked in this case.

And, it seems to me the current proposition of the command trigger
does not support to fire triggers on creation of databases, although
permission checks requires Oid of source database that is not also
appeared in pg_database catalog.

I don't think schemaname+objectname fails to be unique, so I don't think
you need another kind of Oid in BEFORE creation triggers here.

The pg_seclabel and pg_shseclabel needs OID to assign a security label
on a particular database object, so label provider (sepgsql) must know
Oid of the target object on assignment time.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#6Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Kohei KaiGai (#5)
Re: Prep object creation hooks, and related sepgsql updates

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Sorry, it does not cover all the code paths that I want to apply permission
checks around creation of new tables.

The existing DAC checks permission on creation of new tables at
DefineRelation() and OpenIntoRel(), and sepgsql also wants to follow
this manner.
However, OpenIntoRel() does not go through ProcessUtility, so it seems
to me the command trigger is not invoked in this case.

we have the same problem in the command trigger patch, we will need to
add specific calls to its functions from other code path than just
ProcessUtility.

And, it seems to me the current proposition of the command trigger
does not support to fire triggers on creation of databases, although
permission checks requires Oid of source database that is not also
appeared in pg_database catalog.

I have to have a look at what forbids us to add support for the create
database command here. It seems to be just another branch of the switch
in standard_ProcessUtility().

I don't think schemaname+objectname fails to be unique, so I don't think
you need another kind of Oid in BEFORE creation triggers here.

The pg_seclabel and pg_shseclabel needs OID to assign a security label
on a particular database object, so label provider (sepgsql) must know
Oid of the target object on assignment time.

Yes, and you need to refer to things you did in the BEFORE trigger from
the AFTER trigger, I'm just offering you a way to do that. Then if you
need the Oid in the AFTER trigger, of course you have it.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#7Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Dimitri Fontaine (#6)
Re: Prep object creation hooks, and related sepgsql updates

2011/11/27 Dimitri Fontaine <dimitri@2ndquadrant.fr>:

And, it seems to me the current proposition of the command trigger
does not support to fire triggers on creation of databases, although
permission checks requires Oid of source database that is not also
appeared in pg_database catalog.

I have to have a look at what forbids us to add support for the create
database command here.  It seems to be just another branch of the switch
in standard_ProcessUtility().

I'm not sure what arguments shall be provided to the guest of command
triggers. And, I'd like to mention about its specification should not depend
on details of particular modules; according to the previous discussion.

After the long discussion, one concensus is that prep-creation hook shall
be deployed around existing DAC checks and it takes arguments that are
also referenced at the existing DAC; such as oid of source database on
creation of databases.
I also checked security model between DAC and MAC, and I concluded
most of them takes common information to make its decision.

It is a hard to answer question whether we can implement sepgsql on the
upcoming command trigger feature; without information about where its
hooks shall be deployed and what arguments are provided. :-(

I don't think schemaname+objectname fails to be unique, so I don't think
you need another kind of Oid in BEFORE creation triggers here.

The pg_seclabel and pg_shseclabel needs OID to assign a security label
on a particular database object, so label provider (sepgsql) must know
Oid of the target object on assignment time.

Yes, and you need to refer to things you did in the BEFORE trigger from
the AFTER trigger, I'm just offering you a way to do that.  Then if you
need the Oid in the AFTER trigger, of course you have it.

How does it inherit an opaque private initialized at BEFORE trigger to
AFTER trigger? I checked your patch, however, it seems to me it does
not have a mechanism to deliver something between BEFORE and AFTER.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#8Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Kohei KaiGai (#7)
Re: Prep object creation hooks, and related sepgsql updates

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

How does it inherit an opaque private initialized at BEFORE trigger to
AFTER trigger? I checked your patch, however, it seems to me it does
not have a mechanism to deliver something between BEFORE and AFTER.

Right, there's no such facility provided in there. But it seems to me
that your extension could attach some shared memory or use other
mechanisms to handle that on its own?

I'm not trying to force you into using command triggers, just to
determine if that's a road we are able to take.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#9Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Dimitri Fontaine (#8)
Re: Prep object creation hooks, and related sepgsql updates

2011/11/28 Dimitri Fontaine <dimitri@2ndquadrant.fr>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

How does it inherit an opaque private initialized at BEFORE trigger to
AFTER trigger? I checked your patch, however, it seems to me it does
not have a mechanism to deliver something between BEFORE and AFTER.

Right, there's no such facility provided in there.  But it seems to me
that your extension could attach some shared memory or use other
mechanisms to handle that on its own?

Hmm. If extension side manage the contextual information by itself, it seems
to me feasible, although it is a bit bother.
I found up a similar idea that acquires control on ProcessUtility_hook and
save necessary contextual information on auto variable then kicks the
original ProcessUtility_hook, then it reference the contextual information
from object_access_hook.

For example, we don't want to apply permission checks on new relations
constructed with make_new_heap. It shall be invoked when CLUSTER,
VACUUM or ALTER TABLE, so we can skip permission checks when
the saved command tag indicates these commands are currently running.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#10Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Kohei KaiGai (#9)
1 attachment(s)
Re: Prep object creation hooks, and related sepgsql updates

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

I found up a similar idea that acquires control on ProcessUtility_hook and
save necessary contextual information on auto variable then kicks the
original ProcessUtility_hook, then it reference the contextual information
from object_access_hook.

In this case that would be an INSTEAD OF trigger, from which you can
call the original command with EXECUTE. You just have to protect
yourself against infinite recursion, but that's doable. See attached
example.

For example, we don't want to apply permission checks on new relations
constructed with make_new_heap. It shall be invoked when CLUSTER,
VACUUM or ALTER TABLE, so we can skip permission checks when
the saved command tag indicates these commands are currently running.

CREATE TRIGGER se_permission_checks
INSTEAD OF COMMAND ALTER TABLE
EXECUTE PROCEDURE se_permission_checks_alter_table();

In this INSTEAD OF trigger, protect against recursion, EXECUTE the
original ALTER TABLE statement which is given to you as a parameter,
enable the command trigger again.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

Attachments:

cmdtrigger.ext.sqlapplication/octet-streamDownload
#11Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Dimitri Fontaine (#10)
1 attachment(s)
Re: Prep object creation hooks, and related sepgsql updates

2011/11/28 Dimitri Fontaine <dimitri@2ndquadrant.fr>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

I found up a similar idea that acquires control on ProcessUtility_hook and
save necessary contextual information on auto variable then kicks the
original ProcessUtility_hook, then it reference the contextual information
from object_access_hook.

In this case that would be an INSTEAD OF trigger, from which you can
call the original command with EXECUTE. You just have to protect
yourself against infinite recursion, but that's doable. See attached
example.

Hmm... In my case, it does not need to depend on the command trigger.
Let's see the attached patch; that hooks ProcessUtility_hook by
sepgsql_utility_command, then it saves contextual information on auto
variables.

The object_access_hook with OAT_POST_CREATE shall be invoked
from createdb() that was also called by standard_ProcessUtility.
In this context, sepgsql_database_post_create can reference
a property that is not contained withint pg_database catalog
(name of the source database).

At least, it may be able to solve my issues on hooks around object
creation time.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-prep-creation-hook-part-1.v2.database.patchapplication/octet-stream; name=pgsql-v9.2-prep-creation-hook-part-1.v2.database.patchDownload
 contrib/sepgsql/database.c          |  100 +++++++++++++++++++++++++++++++--
 contrib/sepgsql/expected/create.out |   19 ++++++
 contrib/sepgsql/hooks.c             |  104 +++++++++++++++++++++++------------
 contrib/sepgsql/sepgsql.h           |   10 +++
 contrib/sepgsql/sql/create.sql      |   15 +++++
 contrib/sepgsql/test_sepgsql        |    2 +-
 6 files changed, 208 insertions(+), 42 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 7f15d9c..ce5a095 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -10,35 +10,122 @@
  */
 #include "postgres.h"
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/pg_database.h"
+#include "catalog/indexing.h"
+#include "commands/dbcommands.h"
 #include "commands/seclabel.h"
+#include "utils/fmgroids.h"
+#include "utils/tqual.h"
 #include "sepgsql.h"
 
+/*
+ * sepgsql_database_get_catalog
+ *
+ * This routine tries to reference pg_database catalog with supplied OID and
+ * SnapshotSelf visibility, then returns a copy of the HeapTuple.
+ */
+static HeapTuple
+sepgsql_database_get_catalog(Oid databaseId)
+{
+	Relation	catalog;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	catalog = heap_open(DatabaseRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(databaseId));
+
+	sscan = systable_beginscan(catalog, DatabaseOidIndexId, true,
+							   SnapshotSelf, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "catalog lookup failed for database %u", databaseId);
+
+	tuple = heap_copytuple(tuple);
+
+	systable_endscan(sscan);
+	heap_close(catalog, AccessShareLock);
+
+	return tuple;
+}
+
+/*
+ * sepgsql_database_post_create
+ *
+ * This routine assigns a default security label on a newly defined
+ * database, and check permission needed for its creation.
+ */
 void
 sepgsql_database_post_create(Oid databaseId)
 {
 	char   *scontext = sepgsql_get_client_label();
 	char   *tcontext;
 	char   *ncontext;
+	char   *dtemplate;
+	char	audit_name[NAMEDATALEN + 20];
 	ObjectAddress	object;
+	HeapTuple		dattup;
+
+	Assert(sepgsql_context_info.cmdtype == T_CreatedbStmt);
+
+	/*
+	 * Because OID of source database is not saved within pg_database
+	 * catalog entry, we pick up it from the contextual information
+	 * saved on entrypoint of ProcessUtility
+	 */
+	dtemplate = sepgsql_context_info.createdb_template;
+	if (!dtemplate)
+		dtemplate = "template0";
+
+	object.classId = DatabaseRelationId;
+	object.objectId = get_database_oid(dtemplate, false);
+	object.objectSubId = 0;
+
+	tcontext = sepgsql_get_label(object.classId,
+								 object.objectId,
+								 object.objectSubId);
+	/*
+	 * check db_database:{getattr} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name), "database %s", dtemplate);
+	sepgsql_avc_check_perms_label(tcontext,
+								  SEPG_CLASS_DB_DATABASE,
+								  SEPG_DB_DATABASE__GETATTR,
+								  audit_name,
+								  true);
 
 	/*
 	 * Compute a default security label of the newly created database
 	 * based on a pair of security label of client and source database.
 	 *
-	 * XXX - Right now, this logic uses "template1" as its source, because
-	 * here is no way to know the Oid of source database.
+	 * XXX - uncoming version of libselinux supports to take object
+	 * name to handle special treatment on default security label.
 	 */
-	object.classId = DatabaseRelationId;
-	object.objectId = TemplateDbOid;
-	object.objectSubId = 0;
-	tcontext = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG);
+	dattup = sepgsql_database_get_catalog(databaseId);
 
 	ncontext = sepgsql_compute_create(scontext, tcontext,
 									  SEPG_CLASS_DB_DATABASE);
 
 	/*
+	 * check db_database:{create} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name), "database %s",
+			 NameStr(((Form_pg_database) GETSTRUCT(dattup))->datname));
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_DATABASE,
+								  SEPG_DB_DATABASE__CREATE,
+								  audit_name,
+								  true);
+	/*
 	 * Assign the default security label on the new database
 	 */
 	object.classId = DatabaseRelationId;
@@ -47,6 +134,7 @@ sepgsql_database_post_create(Oid databaseId)
 
 	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
 
+	heap_freetuple(dattup);
 	pfree(ncontext);
 	pfree(tcontext);
 }
diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
new file mode 100644
index 0000000..cc60118
--- /dev/null
+++ b/contrib/sepgsql/expected/create.out
@@ -0,0 +1,19 @@
+--
+-- Regression Test for Creation of Object Permission Checks
+--
+-- confirm required permissions using audit messages
+SELECT sepgsql_getcon();	-- confirm client privilege
+              sepgsql_getcon               
+-------------------------------------------
+ unconfined_u:unconfined_r:unconfined_t:s0
+(1 row)
+
+SET sepgsql.debug_audit = true;
+SET client_min_messages = LOG;
+CREATE DATABASE regtest_sepgsql_test_database;
+LOG:  SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template0"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database regtest_sepgsql_test_database"
+--
+-- clean-up
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 331bbd7..bbc9a82 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -31,6 +31,7 @@ PG_MODULE_MAGIC;
  * Declarations
  */
 void		_PG_init(void);
+sepgsql_context_info_t   sepgsql_context_info;
 
 /*
  * Saved hook entries (if stacked)
@@ -308,44 +309,74 @@ sepgsql_utility_command(Node *parsetree,
 						DestReceiver *dest,
 						char *completionTag)
 {
-	if (next_ProcessUtility_hook)
-		(*next_ProcessUtility_hook) (parsetree, queryString, params,
-									 isTopLevel, dest, completionTag);
+	sepgsql_context_info_t	saved_context_info = sepgsql_context_info;
+	ListCell	   *cell;
 
-	/*
-	 * Check command tag to avoid nefarious operations
-	 */
-	switch (nodeTag(parsetree))
+	PG_TRY();
 	{
-		case T_LoadStmt:
-
-			/*
-			 * We reject LOAD command across the board on enforcing mode,
-			 * because a binary module can arbitrarily override hooks.
-			 */
-			if (sepgsql_getenforce())
-			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-						 errmsg("SELinux: LOAD is not permitted")));
-			}
-			break;
-		default:
-
-			/*
-			 * Right now we don't check any other utility commands, because it
-			 * needs more detailed information to make access control decision
-			 * here, but we don't want to have two parse and analyze routines
-			 * individually.
-			 */
-			break;
+		/*
+		 * Check command tag to avoid nefarious operations, and save the
+		 * current contextual information to determine whether we should
+		 * apply permission checks here, or not.
+		 */
+		sepgsql_context_info.cmdtype = nodeTag(parsetree);
+
+		switch (nodeTag(parsetree))
+		{
+			case T_CreatedbStmt:
+				/*
+				 * We hope to reference name of the source database, but it
+				 * does not appear in system catalog. So, we save it here.
+				 */
+				foreach (cell, ((CreatedbStmt *) parsetree)->options)
+				{
+					DefElem	   *defel = (DefElem *) lfirst(cell);
+
+					if (strcmp(defel->defname, "template") == 0)
+					{
+						sepgsql_context_info.createdb_template
+							= strVal(defel->arg);
+						break;
+					}
+				}
+				break;
+
+			case T_LoadStmt:
+				/*
+				 * We reject LOAD command across the board on enforcing mode,
+				 * because a binary module can arbitrarily override hooks.
+				 */
+				if (sepgsql_getenforce())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("SELinux: LOAD is not permitted")));
+				}
+				break;
+			default:
+				/*
+				 * Right now we don't check any other utility commands,
+				 * because it needs more detailed information to make access
+				 * control decision here, but we don't want to have two parse
+				 * and analyze routines individually.
+				 */
+				break;
+		}
+
+		if (next_ProcessUtility_hook)
+			(*next_ProcessUtility_hook) (parsetree, queryString, params,
+										 isTopLevel, dest, completionTag);
+		else
+			standard_ProcessUtility(parsetree, queryString, params,
+									isTopLevel, dest, completionTag);
 	}
-
-	/*
-	 * Original implementation
-	 */
-	standard_ProcessUtility(parsetree, queryString, params,
-							isTopLevel, dest, completionTag);
+	PG_CATCH();
+	{
+		sepgsql_context_info = saved_context_info;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+	sepgsql_context_info = saved_context_info;
 }
 
 /*
@@ -456,4 +487,7 @@ _PG_init(void)
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
 	ProcessUtility_hook = sepgsql_utility_command;
+
+	/* init contextual info */
+	memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
 }
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index b4c1dfd..f192fd4 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -213,6 +213,16 @@
 /*
  * hooks.c
  */
+typedef struct
+{
+	NodeTag		cmdtype;
+
+	/* User given template database on CREATE DATABASE, elsewhere NULL */
+	char	   *createdb_template;
+} sepgsql_context_info_t;
+
+extern sepgsql_context_info_t	sepgsql_context_info;
+
 extern bool sepgsql_get_permissive(void);
 extern bool sepgsql_get_debug_audit(void);
 
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
new file mode 100644
index 0000000..6cd5656
--- /dev/null
+++ b/contrib/sepgsql/sql/create.sql
@@ -0,0 +1,15 @@
+--
+-- Regression Test for Creation of Object Permission Checks
+--
+
+-- confirm required permissions using audit messages
+-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0
+SET sepgsql.debug_audit = true;
+SET client_min_messages = LOG;
+
+CREATE DATABASE regtest_sepgsql_test_database;
+
+--
+-- clean-up
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql
index 9b7262a..52237e6 100755
--- a/contrib/sepgsql/test_sepgsql
+++ b/contrib/sepgsql/test_sepgsql
@@ -259,6 +259,6 @@ echo "found ${NUM}"
 echo
 echo "============== running sepgsql regression tests       =============="
 
-make REGRESS="label dml misc" REGRESS_OPTS="--launcher ./launcher" installcheck
+make REGRESS="label dml create misc" REGRESS_OPTS="--launcher ./launcher" installcheck
 
 # exit with the exit code provided by "make"
#12Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#11)
4 attachment(s)
Re: Prep object creation hooks, and related sepgsql updates

I tried to implement remaining portion of the object creation permission patches
using this approach; that temporary saves contextual information using existing
ProcessUtility hook and ExecutorStart hook.

It likely works fine towards my first problem; system catalog entry
does not have
all the information that requires to make access control decision. In
the case of
pg_database catalog, it does not inform us which database was its source.

Also it maybe works towards my second problem; some of code paths internally
used invokes object-access-hook with OAT_POST_CREATE, so entension is
unavailable to decide whether permission checks should be applied, or not.
In the case of pg_class, heap_create_with_catalog() is invoked by
make_new_heap(),
not only DefineRelation() and OpenIntoRel().
So, this patch checks which statement eventually calls these routines to decide
necessity of permission checks.

All I did is a simple hack on ProcessUtility hook and ExecutorStart hook, then
post-creation-hook references the saved contextual information, as follows.

sepgsql_utility_command(...)
{
sepgsql_context_info_t saved_context_info = sepgsql_context_info;

PG_TRY()
{
sepgsql_context_info.cmdtype = nodeTag(parsetree);
:
if (next_ProcessUtility_hook)
(*next_ProcessUtility_hook) (....)
else
standard_ProcessUtility(....)
}
PG_CATCH();
{
sepgsql_context_info = saved_context_info;
PG_RE_THROW();
}
PG_END_TRY();
sepgsql_context_info = saved_context_info;
}

Then,

sepgsql_relation_post_create(....)
{
:
/*
* Some internally used code paths call heap_create_with_catalog(), then
* it launches this hook, even though it does not need permission check
* on creation of relation. So, we skip these cases.
*/
switch (sepgsql_context_info.cmdtype)
{
case T_CreateStmt:
case T_ViewStmt:
case T_CreateSeqStmt:
case T_CompositeTypeStmt:
case T_CreateForeignTableStmt:
case T_SelectStmt:
break;
default:
/* internal calls */
return;
}
:
}

At least, it is working. However, it is not a perfect solution to the
future updates
of code paths in the core.

Thanks,

2011/11/29 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2011/11/28 Dimitri Fontaine <dimitri@2ndquadrant.fr>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

I found up a similar idea that acquires control on ProcessUtility_hook and
save necessary contextual information on auto variable then kicks the
original ProcessUtility_hook, then it reference the contextual information
from object_access_hook.

In this case that would be an INSTEAD OF trigger, from which you can
call the original command with EXECUTE. You just have to protect
yourself against infinite recursion, but that's doable. See attached
example.

Hmm... In my case, it does not need to depend on the command trigger.
Let's see the attached patch; that hooks ProcessUtility_hook by
sepgsql_utility_command, then it saves contextual information on auto
variables.

The object_access_hook with OAT_POST_CREATE shall be invoked
from createdb() that was also called by standard_ProcessUtility.
In this context, sepgsql_database_post_create can reference
a property that is not contained withint pg_database catalog
(name of the source database).

At least, it may be able to solve my issues on hooks around object
creation time.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-sepgsql-create-permissions-part-3.relation.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-create-permissions-part-3.relation.patchDownload
 contrib/sepgsql/expected/create.out |   30 +++++++
 contrib/sepgsql/hooks.c             |   45 ++++++++++
 contrib/sepgsql/relation.c          |  163 ++++++++++++++++++++++++++++++-----
 contrib/sepgsql/sql/create.sql      |   12 +++
 4 files changed, 227 insertions(+), 23 deletions(-)

diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index 230e6dd..d2c30f0 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -16,8 +16,38 @@ LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_
 CREATE SCHEMA regtest_schema;
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
 SET search_path = regtest_schema, public;
+CREATE TABLE regtest_table (x serial primary key, y text);
+NOTICE:  CREATE TABLE will create implicit sequence "regtest_table_x_seq" for serial column "regtest_table.x"
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_table_x_seq"
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="table regtest_table"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column tableoid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmax"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmax"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmin"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmin"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column ctid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column x"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column y"
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "regtest_table_pkey" for table "regtest_table"
+ALTER TABLE regtest_table ADD COLUMN z int;
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column z"
+-- vacuum full internally create a new table and swap it later.
+-- it does not require permission to create table/columns
+VACUUM FULL regtest_table;
+CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100;
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="view regtest_view"
+CREATE SEQUENCE regtest_seq;
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_seq"
 --
 -- clean-up
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
 DROP SCHEMA IF EXISTS regtest_schema CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table regtest_table
+drop cascades to view regtest_view
+drop cascades to sequence regtest_seq
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index bbc9a82..bff8918 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -42,6 +42,7 @@ static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
 static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
 static fmgr_hook_type next_fmgr_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
 
 /*
  * GUC: sepgsql.permissive = (on|off)
@@ -296,6 +297,46 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 }
 
 /*
+ * sepgsql_executor_start
+ *
+ * It saves contextual information during ExecutorStart to distinguish 
+ * a case with/without permission checks later.
+ */
+static void
+sepgsql_executor_start(QueryDesc *queryDesc, int eflags)
+{
+	sepgsql_context_info_t	saved_context_info = sepgsql_context_info;
+
+	PG_TRY();
+	{
+		if (queryDesc->operation == CMD_SELECT)
+			sepgsql_context_info.cmdtype = T_SelectStmt;
+		else if (queryDesc->operation == CMD_INSERT)
+			sepgsql_context_info.cmdtype = T_InsertStmt;
+		else if (queryDesc->operation == CMD_DELETE)
+			sepgsql_context_info.cmdtype = T_DeleteStmt;
+		else if (queryDesc->operation == CMD_UPDATE)
+			sepgsql_context_info.cmdtype = T_UpdateStmt;
+
+		/*
+		 * XXX - If queryDesc->operation is not above four cases, an error
+		 * shall be raised on the following executor stage soon.
+		 */
+		if (next_ExecutorStart_hook)
+			(*next_ExecutorStart_hook) (queryDesc, eflags);
+		else
+			standard_ExecutorStart(queryDesc, eflags);
+	}
+	PG_CATCH();
+	{
+		sepgsql_context_info = saved_context_info;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+	sepgsql_context_info = saved_context_info;
+}
+
+/*
  * sepgsql_utility_command
  *
  * It tries to rough-grained control on utility commands; some of them can
@@ -488,6 +529,10 @@ _PG_init(void)
 	next_ProcessUtility_hook = ProcessUtility_hook;
 	ProcessUtility_hook = sepgsql_utility_command;
 
+	/* ExecutorStart hook */
+	next_ExecutorStart_hook = ExecutorStart_hook;
+	ExecutorStart_hook = sepgsql_executor_start;
+
 	/* init contextual info */
 	memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
 }
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 0767382..a4ea22c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -36,10 +36,16 @@
 void
 sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 {
-	char	   *scontext = sepgsql_get_client_label();
+	Relation	rel;
+	ScanKeyData skey[2];
+	SysScanDesc sscan;
+	HeapTuple	tuple;
+	char	   *scontext;
 	char	   *tcontext;
 	char	   *ncontext;
+	char		audit_name[2*NAMEDATALEN + 20];
 	ObjectAddress object;
+	Form_pg_attribute	attForm;
 
 	/*
 	 * Only attributes within regular relation have individual security
@@ -49,13 +55,44 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 		return;
 
 	/*
-	 * Compute a default security label when we create a new procedure object
-	 * under the specified namespace.
+	 * Compute a default security label of the new column underlying the
+	 * specified relation, and check permission to create it.
 	 */
+	rel = heap_open(AttributeRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey[0],
+				Anum_pg_attribute_attrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relOid));
+	ScanKeyInit(&skey[1],
+				Anum_pg_attribute_attnum,
+				BTEqualStrategyNumber, F_INT2EQ,
+				Int16GetDatum(attnum));
+
+	sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true,
+							   SnapshotSelf, 2, &skey[0]);
+
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "catalog lookup failed for column %d of relation %u",
+			 attnum, relOid);
+
+	attForm = (Form_pg_attribute) GETSTRUCT(tuple);
+
 	scontext = sepgsql_get_client_label();
 	tcontext = sepgsql_get_label(RelationRelationId, relOid, 0);
 	ncontext = sepgsql_compute_create(scontext, tcontext,
 									  SEPG_CLASS_DB_COLUMN);
+	/*
+	 * check db_column:{create} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name), "table %s column %s",
+			 get_rel_name(relOid), NameStr(attForm->attname));
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_COLUMN,
+								  SEPG_DB_COLUMN__CREATE,
+								  audit_name,
+								  true);
 
 	/*
 	 * Assign the default security label on a new procedure
@@ -65,6 +102,9 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 	object.objectSubId = attnum;
 	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
 
+	systable_endscan(sscan);
+	heap_close(rel, AccessShareLock);
+
 	pfree(tcontext);
 	pfree(ncontext);
 }
@@ -127,10 +167,31 @@ sepgsql_relation_post_create(Oid relOid)
 	Form_pg_class classForm;
 	ObjectAddress object;
 	uint16		tclass;
+	const char *tclass_text;
 	char	   *scontext;		/* subject */
 	char	   *tcontext;		/* schema */
 	char	   *rcontext;		/* relation */
 	char	   *ccontext;		/* column */
+	char		audit_name[2*NAMEDATALEN + 20];
+
+	/*
+	 * Some internally used code paths call heap_create_with_catalog(), then
+	 * it launches this hook, even though it does not need permission check
+	 * on creation of relation. So, we skip these cases.
+	 */
+	switch (sepgsql_context_info.cmdtype)
+	{
+		case T_CreateStmt:
+		case T_ViewStmt:
+		case T_CreateSeqStmt:
+		case T_CompositeTypeStmt:
+		case T_CreateForeignTableStmt:
+		case T_SelectStmt:
+			break;
+		default:
+			/* internal calls */
+			return;
+	}
 
 	/*
 	 * Fetch catalog record of the new relation. Because pg_class entry is not
@@ -152,16 +213,36 @@ sepgsql_relation_post_create(Oid relOid)
 
 	classForm = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classForm->relkind == RELKIND_RELATION)
-		tclass = SEPG_CLASS_DB_TABLE;
-	else if (classForm->relkind == RELKIND_SEQUENCE)
-		tclass = SEPG_CLASS_DB_SEQUENCE;
-	else if (classForm->relkind == RELKIND_VIEW)
-		tclass = SEPG_CLASS_DB_VIEW;
-	else
-		goto out;				/* No need to assign individual labels */
+	switch (classForm->relkind)
+	{
+		case RELKIND_RELATION:
+			tclass = SEPG_CLASS_DB_TABLE;
+			tclass_text = "table";
+			break;
+		case RELKIND_SEQUENCE:
+			tclass = SEPG_CLASS_DB_SEQUENCE;
+			tclass_text = "sequence";
+			break;
+		case RELKIND_VIEW:
+			tclass = SEPG_CLASS_DB_VIEW;
+			tclass_text = "view";
+			break;
+		default:
+			goto out;
+	}
 
 	/*
+	 * check db_schema:{add_name} permission of the namespace
+	 */
+	object.classId = NamespaceRelationId;
+	object.objectId = classForm->relnamespace;
+	object.objectSubId = 0;
+	sepgsql_avc_check_perms(&object,
+							SEPG_CLASS_DB_SCHEMA,
+							SEPG_DB_SCHEMA__ADD_NAME,
+							getObjectDescription(&object),
+							true);
+	/*
 	 * Compute a default security label when we create a new relation object
 	 * under the specified namespace.
 	 */
@@ -171,6 +252,16 @@ sepgsql_relation_post_create(Oid relOid)
 	rcontext = sepgsql_compute_create(scontext, tcontext, tclass);
 
 	/*
+	 * check db_xxx:{create} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name), "%s %s",
+			 tclass_text, NameStr(classForm->relname));
+	sepgsql_avc_check_perms_label(rcontext,
+								  tclass,
+								  SEPG_DB_DATABASE__CREATE,
+								  audit_name,
+								  true);
+	/*
 	 * Assign the default security label on the new relation
 	 */
 	object.classId = RelationRelationId;
@@ -184,26 +275,52 @@ sepgsql_relation_post_create(Oid relOid)
 	 */
 	if (classForm->relkind == RELKIND_RELATION)
 	{
-		AttrNumber	index;
+		Relation	arel;
+		ScanKeyData	akey;
+		SysScanDesc	ascan;
+		HeapTuple	atup;
+		Form_pg_attribute	attForm;
+
+		arel = heap_open(AttributeRelationId, AccessShareLock);
+
+		ScanKeyInit(&akey,
+					Anum_pg_attribute_attrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relOid));
 
-		ccontext = sepgsql_compute_create(scontext, rcontext,
-										  SEPG_CLASS_DB_COLUMN);
-		for (index = FirstLowInvalidHeapAttributeNumber + 1;
-			 index <= classForm->relnatts;
-			 index++)
+		ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true,
+								   SnapshotSelf, 1, &akey);
+
+		while (HeapTupleIsValid(atup = systable_getnext(ascan)))
 		{
-			if (index == InvalidAttrNumber)
-				continue;
+			attForm = (Form_pg_attribute) GETSTRUCT(atup);
+
+			snprintf(audit_name, sizeof(audit_name), "%s %s column %s",
+					 tclass_text,
+					 NameStr(classForm->relname),
+					 NameStr(attForm->attname));
 
-			if (index == ObjectIdAttributeNumber && !classForm->relhasoids)
-				continue;
+			ccontext = sepgsql_compute_create(scontext,
+											  rcontext,
+											  SEPG_CLASS_DB_COLUMN);
+			/*
+			 * check db_column:{create} permission
+			 */
+			sepgsql_avc_check_perms_label(ccontext,
+										  SEPG_CLASS_DB_COLUMN,
+										  SEPG_DB_COLUMN__CREATE,
+										  audit_name,
+										  true);
 
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
-			object.objectSubId = index;
+			object.objectSubId = attForm->attnum;
 			SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext);
+
+			pfree(ccontext);
 		}
-		pfree(ccontext);
+		systable_endscan(ascan);
+		heap_close(arel, AccessShareLock);
 	}
 	pfree(rcontext);
 out:
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index a03c977..9cb2b60 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -13,6 +13,18 @@ CREATE SCHEMA regtest_schema;
 
 SET search_path = regtest_schema, public;
 
+CREATE TABLE regtest_table (x serial primary key, y text);
+
+ALTER TABLE regtest_table ADD COLUMN z int;
+
+-- vacuum full internally create a new table and swap it later.
+-- it does not require permission to create table/columns
+VACUUM FULL regtest_table;
+
+CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100;
+
+CREATE SEQUENCE regtest_seq;
+
 --
 -- clean-up
 --
pgsql-v9.2-sepgsql-create-permissions-part-2.namespace.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-create-permissions-part-2.namespace.patchDownload
 contrib/sepgsql/expected/create.out |    4 +++
 contrib/sepgsql/schema.c            |   50 ++++++++++++++++++++++++++++++++---
 contrib/sepgsql/sql/create.sql      |    6 ++++
 3 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index cc60118..230e6dd 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -13,7 +13,11 @@ SET client_min_messages = LOG;
 CREATE DATABASE regtest_sepgsql_test_database;
 LOG:  SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template0"
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database regtest_sepgsql_test_database"
+CREATE SCHEMA regtest_schema;
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+SET search_path = regtest_schema, public;
 --
 -- clean-up
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
+DROP SCHEMA IF EXISTS regtest_schema CASCADE;
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index a167be1..c8bb8c9 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -10,12 +10,18 @@
  */
 #include "postgres.h"
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/sysattr.h"
 #include "catalog/dependency.h"
+#include "catalog/indexing.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "commands/seclabel.h"
 #include "miscadmin.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/tqual.h"
 
 #include "sepgsql.h"
 
@@ -28,19 +34,55 @@
 void
 sepgsql_schema_post_create(Oid namespaceId)
 {
-	char	   *scontext;
+	Relation	rel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
 	char	   *tcontext;
 	char	   *ncontext;
-	ObjectAddress object;
+	char		audit_name[NAMEDATALEN + 20];
+	ObjectAddress		object;
+	Form_pg_namespace	nspForm;
 
 	/*
 	 * Compute a default security label when we create a new schema object
 	 * under the working database.
+	 *
+	 * XXX - uncoming version of libselinux supports to take object
+	 * name to handle special treatment on default security label;
+	 * such as special label on "pg_temp" schema.
 	 */
-	scontext = sepgsql_get_client_label();
+	rel = heap_open(NamespaceRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(namespaceId));
+
+	sscan = systable_beginscan(rel, NamespaceOidIndexId, true,
+							   SnapshotSelf, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "catalog lookup failed for namespace %u", namespaceId);
+
+	nspForm = (Form_pg_namespace) GETSTRUCT(tuple);
+
 	tcontext = sepgsql_get_label(DatabaseRelationId, MyDatabaseId, 0);
-	ncontext = sepgsql_compute_create(scontext, tcontext,
+	ncontext = sepgsql_compute_create(sepgsql_get_client_label(),
+									  tcontext,
 									  SEPG_CLASS_DB_SCHEMA);
+	/*
+	 * check db_schema:{create}
+	 */
+	snprintf(audit_name, sizeof(audit_name),
+			 "schema %s", NameStr(nspForm->nspname));
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_SCHEMA,
+								  SEPG_DB_SCHEMA__CREATE,
+								  audit_name,
+								  true);
+	systable_endscan(sscan);
+	heap_close(rel, AccessShareLock);
 
 	/*
 	 * Assign the default security label on a new procedure
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index 6cd5656..a03c977 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -9,7 +9,13 @@ SET client_min_messages = LOG;
 
 CREATE DATABASE regtest_sepgsql_test_database;
 
+CREATE SCHEMA regtest_schema;
+
+SET search_path = regtest_schema, public;
+
 --
 -- clean-up
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
+
+DROP SCHEMA IF EXISTS regtest_schema CASCADE;
pgsql-v9.2-sepgsql-create-permissions-part-1.database.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-create-permissions-part-1.database.patchDownload
 contrib/sepgsql/database.c          |   93 +++++++++++++++++++++++++++----
 contrib/sepgsql/expected/create.out |   19 ++++++
 contrib/sepgsql/hooks.c             |  104 +++++++++++++++++++++++------------
 contrib/sepgsql/sepgsql.h           |   10 +++
 contrib/sepgsql/sql/create.sql      |   15 +++++
 contrib/sepgsql/test_sepgsql        |    2 +-
 6 files changed, 196 insertions(+), 47 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 7f15d9c..65941c4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -10,33 +10,104 @@
  */
 #include "postgres.h"
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/pg_database.h"
+#include "catalog/indexing.h"
+#include "commands/dbcommands.h"
 #include "commands/seclabel.h"
+#include "utils/fmgroids.h"
+#include "utils/tqual.h"
 #include "sepgsql.h"
 
+/*
+ * sepgsql_database_post_create
+ *
+ * This routine assigns a default security label on a newly defined
+ * database, and check permission needed for its creation.
+ */
 void
 sepgsql_database_post_create(Oid databaseId)
 {
-	char   *scontext = sepgsql_get_client_label();
-	char   *tcontext;
-	char   *ncontext;
-	ObjectAddress	object;
+	Relation	rel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+	char	   *tcontext;
+	char	   *ncontext;
+	char	   *dtemplate;
+	char		audit_name[NAMEDATALEN + 20];
+	ObjectAddress		object;
+	Form_pg_database	datForm;
+
+	Assert(sepgsql_context_info.cmdtype == T_CreatedbStmt);
+
+	/*
+	 * Because OID of source database is not saved within pg_database
+	 * catalog entry, we pick up it from the contextual information
+	 * saved on entrypoint of ProcessUtility
+	 */
+	dtemplate = sepgsql_context_info.createdb_template;
+	if (!dtemplate)
+		dtemplate = "template0";
+
+	object.classId = DatabaseRelationId;
+	object.objectId = get_database_oid(dtemplate, false);
+	object.objectSubId = 0;
+
+	tcontext = sepgsql_get_label(object.classId,
+								 object.objectId,
+								 object.objectSubId);
+	/*
+	 * check db_database:{getattr} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name), "database %s", dtemplate);
+	sepgsql_avc_check_perms_label(tcontext,
+								  SEPG_CLASS_DB_DATABASE,
+								  SEPG_DB_DATABASE__GETATTR,
+								  audit_name,
+								  true);
 
 	/*
 	 * Compute a default security label of the newly created database
 	 * based on a pair of security label of client and source database.
 	 *
-	 * XXX - Right now, this logic uses "template1" as its source, because
-	 * here is no way to know the Oid of source database.
+	 * XXX - uncoming version of libselinux supports to take object
+	 * name to handle special treatment on default security label.
 	 */
-	object.classId = DatabaseRelationId;
-	object.objectId = TemplateDbOid;
-	object.objectSubId = 0;
-	tcontext = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG);
+	rel = heap_open(DatabaseRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(databaseId));
+
+	sscan = systable_beginscan(rel, DatabaseOidIndexId, true,
+							   SnapshotSelf, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "catalog lookup failed for database %u", databaseId);
 
-	ncontext = sepgsql_compute_create(scontext, tcontext,
+	datForm = (Form_pg_database) GETSTRUCT(tuple);
+
+	ncontext = sepgsql_compute_create(sepgsql_get_client_label(),
+									  tcontext,
 									  SEPG_CLASS_DB_DATABASE);
+	/*
+	 * check db_database:{create} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name),
+			 "database %s", NameStr(datForm->datname));
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_DATABASE,
+								  SEPG_DB_DATABASE__CREATE,
+								  audit_name,
+								  true);
+
+	systable_endscan(sscan);
+	heap_close(rel, AccessShareLock);
 
 	/*
 	 * Assign the default security label on the new database
diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
new file mode 100644
index 0000000..cc60118
--- /dev/null
+++ b/contrib/sepgsql/expected/create.out
@@ -0,0 +1,19 @@
+--
+-- Regression Test for Creation of Object Permission Checks
+--
+-- confirm required permissions using audit messages
+SELECT sepgsql_getcon();	-- confirm client privilege
+              sepgsql_getcon               
+-------------------------------------------
+ unconfined_u:unconfined_r:unconfined_t:s0
+(1 row)
+
+SET sepgsql.debug_audit = true;
+SET client_min_messages = LOG;
+CREATE DATABASE regtest_sepgsql_test_database;
+LOG:  SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template0"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database regtest_sepgsql_test_database"
+--
+-- clean-up
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 331bbd7..bbc9a82 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -31,6 +31,7 @@ PG_MODULE_MAGIC;
  * Declarations
  */
 void		_PG_init(void);
+sepgsql_context_info_t   sepgsql_context_info;
 
 /*
  * Saved hook entries (if stacked)
@@ -308,44 +309,74 @@ sepgsql_utility_command(Node *parsetree,
 						DestReceiver *dest,
 						char *completionTag)
 {
-	if (next_ProcessUtility_hook)
-		(*next_ProcessUtility_hook) (parsetree, queryString, params,
-									 isTopLevel, dest, completionTag);
+	sepgsql_context_info_t	saved_context_info = sepgsql_context_info;
+	ListCell	   *cell;
 
-	/*
-	 * Check command tag to avoid nefarious operations
-	 */
-	switch (nodeTag(parsetree))
+	PG_TRY();
 	{
-		case T_LoadStmt:
-
-			/*
-			 * We reject LOAD command across the board on enforcing mode,
-			 * because a binary module can arbitrarily override hooks.
-			 */
-			if (sepgsql_getenforce())
-			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-						 errmsg("SELinux: LOAD is not permitted")));
-			}
-			break;
-		default:
-
-			/*
-			 * Right now we don't check any other utility commands, because it
-			 * needs more detailed information to make access control decision
-			 * here, but we don't want to have two parse and analyze routines
-			 * individually.
-			 */
-			break;
+		/*
+		 * Check command tag to avoid nefarious operations, and save the
+		 * current contextual information to determine whether we should
+		 * apply permission checks here, or not.
+		 */
+		sepgsql_context_info.cmdtype = nodeTag(parsetree);
+
+		switch (nodeTag(parsetree))
+		{
+			case T_CreatedbStmt:
+				/*
+				 * We hope to reference name of the source database, but it
+				 * does not appear in system catalog. So, we save it here.
+				 */
+				foreach (cell, ((CreatedbStmt *) parsetree)->options)
+				{
+					DefElem	   *defel = (DefElem *) lfirst(cell);
+
+					if (strcmp(defel->defname, "template") == 0)
+					{
+						sepgsql_context_info.createdb_template
+							= strVal(defel->arg);
+						break;
+					}
+				}
+				break;
+
+			case T_LoadStmt:
+				/*
+				 * We reject LOAD command across the board on enforcing mode,
+				 * because a binary module can arbitrarily override hooks.
+				 */
+				if (sepgsql_getenforce())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("SELinux: LOAD is not permitted")));
+				}
+				break;
+			default:
+				/*
+				 * Right now we don't check any other utility commands,
+				 * because it needs more detailed information to make access
+				 * control decision here, but we don't want to have two parse
+				 * and analyze routines individually.
+				 */
+				break;
+		}
+
+		if (next_ProcessUtility_hook)
+			(*next_ProcessUtility_hook) (parsetree, queryString, params,
+										 isTopLevel, dest, completionTag);
+		else
+			standard_ProcessUtility(parsetree, queryString, params,
+									isTopLevel, dest, completionTag);
 	}
-
-	/*
-	 * Original implementation
-	 */
-	standard_ProcessUtility(parsetree, queryString, params,
-							isTopLevel, dest, completionTag);
+	PG_CATCH();
+	{
+		sepgsql_context_info = saved_context_info;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+	sepgsql_context_info = saved_context_info;
 }
 
 /*
@@ -456,4 +487,7 @@ _PG_init(void)
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
 	ProcessUtility_hook = sepgsql_utility_command;
+
+	/* init contextual info */
+	memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
 }
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index b4c1dfd..f192fd4 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -213,6 +213,16 @@
 /*
  * hooks.c
  */
+typedef struct
+{
+	NodeTag		cmdtype;
+
+	/* User given template database on CREATE DATABASE, elsewhere NULL */
+	char	   *createdb_template;
+} sepgsql_context_info_t;
+
+extern sepgsql_context_info_t	sepgsql_context_info;
+
 extern bool sepgsql_get_permissive(void);
 extern bool sepgsql_get_debug_audit(void);
 
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
new file mode 100644
index 0000000..6cd5656
--- /dev/null
+++ b/contrib/sepgsql/sql/create.sql
@@ -0,0 +1,15 @@
+--
+-- Regression Test for Creation of Object Permission Checks
+--
+
+-- confirm required permissions using audit messages
+-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0
+SET sepgsql.debug_audit = true;
+SET client_min_messages = LOG;
+
+CREATE DATABASE regtest_sepgsql_test_database;
+
+--
+-- clean-up
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql
index 9b7262a..52237e6 100755
--- a/contrib/sepgsql/test_sepgsql
+++ b/contrib/sepgsql/test_sepgsql
@@ -259,6 +259,6 @@ echo "found ${NUM}"
 echo
 echo "============== running sepgsql regression tests       =============="
 
-make REGRESS="label dml misc" REGRESS_OPTS="--launcher ./launcher" installcheck
+make REGRESS="label dml create misc" REGRESS_OPTS="--launcher ./launcher" installcheck
 
 # exit with the exit code provided by "make"
pgsql-v9.2-sepgsql-create-permissions-part-4.proc.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-create-permissions-part-4.proc.patchDownload
 contrib/sepgsql/expected/create.out |   13 +++++++-
 contrib/sepgsql/proc.c              |   55 +++++++++++++++++++++++++++++++----
 contrib/sepgsql/sql/create.sql      |    7 ++++
 3 files changed, 68 insertions(+), 7 deletions(-)

diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index d2c30f0..ec52700 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -42,12 +42,23 @@ LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_
 CREATE SEQUENCE regtest_seq;
 LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_seq"
+CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql
+	   AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END';
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="function regtest_func(text,integer[])"
+CREATE AGGREGATE regtest_agg (*) (
+	   sfunc = int8inc, stype = int8, initcond = '0'
+);
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="function regtest_agg()"
 --
 -- clean-up
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
 DROP SCHEMA IF EXISTS regtest_schema CASCADE;
-NOTICE:  drop cascades to 3 other objects
+NOTICE:  drop cascades to 5 other objects
 DETAIL:  drop cascades to table regtest_table
 drop cascades to view regtest_view
 drop cascades to sequence regtest_seq
+drop cascades to function regtest_func(text,integer[])
+drop cascades to function regtest_agg()
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index 9630d45..14231c4 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -18,6 +18,7 @@
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
 #include "commands/seclabel.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/tqual.h"
@@ -37,11 +38,13 @@ sepgsql_proc_post_create(Oid functionId)
 	ScanKeyData skey;
 	SysScanDesc sscan;
 	HeapTuple	tuple;
-	Oid			namespaceId;
-	ObjectAddress object;
 	char	   *scontext;
 	char	   *tcontext;
 	char	   *ncontext;
+	int			i;
+	StringInfoData	audit_name;
+	ObjectAddress	object;
+	Form_pg_proc	proForm;
 
 	/*
 	 * Fetch namespace of the new procedure. Because pg_proc entry is not
@@ -61,21 +64,54 @@ sepgsql_proc_post_create(Oid functionId)
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "catalog lookup failed for proc %u", functionId);
 
-	namespaceId = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace;
+	proForm = (Form_pg_proc) GETSTRUCT(tuple);
+
+	/*
+	 * check db_schema:{add_name} permission of the namespace
+	 */
+	object.classId = NamespaceRelationId;
+	object.objectId = proForm->pronamespace;
+	object.objectSubId = 0;
+	sepgsql_avc_check_perms(&object,
+							SEPG_CLASS_DB_SCHEMA,
+							SEPG_DB_SCHEMA__ADD_NAME,
+							getObjectDescription(&object),
+							true);
+	/*
+	 * XXX - db_language:{implement} also should be checked here
+	 */
 
-	systable_endscan(sscan);
-	heap_close(rel, AccessShareLock);
 
 	/*
 	 * Compute a default security label when we create a new procedure object
 	 * under the specified namespace.
 	 */
 	scontext = sepgsql_get_client_label();
-	tcontext = sepgsql_get_label(NamespaceRelationId, namespaceId, 0);
+	tcontext = sepgsql_get_label(NamespaceRelationId,
+								 proForm->pronamespace, 0);
 	ncontext = sepgsql_compute_create(scontext, tcontext,
 									  SEPG_CLASS_DB_PROCEDURE);
 
 	/*
+	 * check db_procedure:{create} permission
+	 */
+	initStringInfo(&audit_name);
+	appendStringInfo(&audit_name, "function %s(", NameStr(proForm->proname));
+	for (i=0; i < proForm->pronargs; i++)
+	{
+		Oid		typeoid = proForm->proargtypes.values[i];
+		if (i > 0)
+			appendStringInfoChar(&audit_name, ',');
+		appendStringInfoString(&audit_name, format_type_be(typeoid));
+	}
+	appendStringInfoChar(&audit_name, ')');
+
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_PROCEDURE,
+								  SEPG_DB_PROCEDURE__CREATE,
+								  audit_name.data,
+								  true);
+	/*
 	 * Assign the default security label on a new procedure
 	 */
 	object.classId = ProcedureRelationId;
@@ -83,6 +119,13 @@ sepgsql_proc_post_create(Oid functionId)
 	object.objectSubId = 0;
 	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
 
+	/*
+	 * Cleanup
+	 */
+	systable_endscan(sscan);
+	heap_close(rel, AccessShareLock);
+
+	pfree(audit_name.data);
 	pfree(tcontext);
 	pfree(ncontext);
 }
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index 9cb2b60..512c114 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -25,6 +25,13 @@ CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100;
 
 CREATE SEQUENCE regtest_seq;
 
+CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql
+	   AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END';
+
+CREATE AGGREGATE regtest_agg (*) (
+	   sfunc = int8inc, stype = int8, initcond = '0'
+);
+
 --
 -- clean-up
 --
#13Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#12)
Re: Prep object creation hooks, and related sepgsql updates

On Fri, Dec 2, 2011 at 6:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

At least, it is working. However, it is not a perfect solution to the
future updates
of code paths in the core.

Hmm. So, do you want this committed? If so, I think the major thing
it lacks is documentation.

I can't help noticing that this amounts, altogether, to less than 600
lines of code. I am not sure what your hesitation in taking this
approach is. Certainly, there are things not to like in here, but
I've seen a lot worse, and you can always refine it later. For a
first cut, why not? Even if you had the absolutely perfect hooks in
core, how much would it save compared to what's here now? How
different would your ideal implementation be from what you've done
here?

As regards future updates of code paths in core, nothing in here looks
terribly likely to get broken; or at least if it does then I think
quite a lot of other things will get broken, too. Anything we do has
some maintenance burden, and this doesn't look particularly bad to me.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#14Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#13)
Re: Prep object creation hooks, and related sepgsql updates

2011/12/3 Robert Haas <robertmhaas@gmail.com>:

On Fri, Dec 2, 2011 at 6:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

At least, it is working. However, it is not a perfect solution to the
future updates
of code paths in the core.

Hmm.  So, do you want this committed?  If so, I think the major thing
it lacks is documentation.

I can't help noticing that this amounts, altogether, to less than 600
lines of code.  I am not sure what your hesitation in taking this
approach is.  Certainly, there are things not to like in here, but
I've seen a lot worse, and you can always refine it later.  For a
first cut, why not?    Even if you had the absolutely perfect hooks in
core, how much would it save compared to what's here now?  How
different would your ideal implementation be from what you've done
here?

You are likely right. Even if the hook provides sepgsql enough
contextual information, it might means maintenance burden being
moved to the core from sepgsql, as we discussed before.
OK, I'd like to go with this approach. I'll try to update documentation
stuff and regression test cases, so please wait for a few days.

Thanks,

As regards future updates of code paths in core, nothing in here looks
terribly likely to get broken; or at least if it does then I think
quite a lot of other things will get broken, too.  Anything we do has
some maintenance burden, and this doesn't look particularly bad to me.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#15Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#14)
4 attachment(s)
Re: Prep object creation hooks, and related sepgsql updates

The attached patches are revised ones.
I added explanations of DDL permissions on creation time added by these patches,
and added a few regression test cases.

Thanks,

2011/12/3 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2011/12/3 Robert Haas <robertmhaas@gmail.com>:

On Fri, Dec 2, 2011 at 6:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

At least, it is working. However, it is not a perfect solution to the
future updates
of code paths in the core.

Hmm.  So, do you want this committed?  If so, I think the major thing
it lacks is documentation.

I can't help noticing that this amounts, altogether, to less than 600
lines of code.  I am not sure what your hesitation in taking this
approach is.  Certainly, there are things not to like in here, but
I've seen a lot worse, and you can always refine it later.  For a
first cut, why not?    Even if you had the absolutely perfect hooks in
core, how much would it save compared to what's here now?  How
different would your ideal implementation be from what you've done
here?

You are likely right. Even if the hook provides sepgsql enough
contextual information, it might means maintenance burden being
moved to the core from sepgsql, as we discussed before.
OK, I'd like to go with this approach. I'll try to update documentation
stuff and regression test cases, so please wait for a few days.

Thanks,

As regards future updates of code paths in core, nothing in here looks
terribly likely to get broken; or at least if it does then I think
quite a lot of other things will get broken, too.  Anything we do has
some maintenance burden, and this doesn't look particularly bad to me.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-sepgsql-create-permissions-part-4.v2.proc.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-create-permissions-part-4.v2.proc.patchDownload
 contrib/sepgsql/expected/create.out |   13 +++++++-
 contrib/sepgsql/proc.c              |   55 +++++++++++++++++++++++++++++++----
 contrib/sepgsql/sql/create.sql      |    7 ++++
 3 files changed, 68 insertions(+), 7 deletions(-)

diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index 9b5c3d1..0f04a3e 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -56,14 +56,25 @@ CREATE SEQUENCE regtest_seq;
 LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_seq"
 CREATE TYPE regtest_comptype AS (a int, b text);
+CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql
+	   AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END';
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="function regtest_func(text,integer[])"
+CREATE AGGREGATE regtest_agg (
+           sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0'
+);
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="function regtest_agg(integer)"
 --
 -- clean-up
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
 DROP SCHEMA IF EXISTS regtest_schema CASCADE;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 7 other objects
 DETAIL:  drop cascades to table regtest_table
 drop cascades to table regtest_table_2
 drop cascades to view regtest_view
 drop cascades to sequence regtest_seq
 drop cascades to type regtest_comptype
+drop cascades to function regtest_func(text,integer[])
+drop cascades to function regtest_agg(integer)
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index 9630d45..14231c4 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -18,6 +18,7 @@
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
 #include "commands/seclabel.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/tqual.h"
@@ -37,11 +38,13 @@ sepgsql_proc_post_create(Oid functionId)
 	ScanKeyData skey;
 	SysScanDesc sscan;
 	HeapTuple	tuple;
-	Oid			namespaceId;
-	ObjectAddress object;
 	char	   *scontext;
 	char	   *tcontext;
 	char	   *ncontext;
+	int			i;
+	StringInfoData	audit_name;
+	ObjectAddress	object;
+	Form_pg_proc	proForm;
 
 	/*
 	 * Fetch namespace of the new procedure. Because pg_proc entry is not
@@ -61,21 +64,54 @@ sepgsql_proc_post_create(Oid functionId)
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "catalog lookup failed for proc %u", functionId);
 
-	namespaceId = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace;
+	proForm = (Form_pg_proc) GETSTRUCT(tuple);
+
+	/*
+	 * check db_schema:{add_name} permission of the namespace
+	 */
+	object.classId = NamespaceRelationId;
+	object.objectId = proForm->pronamespace;
+	object.objectSubId = 0;
+	sepgsql_avc_check_perms(&object,
+							SEPG_CLASS_DB_SCHEMA,
+							SEPG_DB_SCHEMA__ADD_NAME,
+							getObjectDescription(&object),
+							true);
+	/*
+	 * XXX - db_language:{implement} also should be checked here
+	 */
 
-	systable_endscan(sscan);
-	heap_close(rel, AccessShareLock);
 
 	/*
 	 * Compute a default security label when we create a new procedure object
 	 * under the specified namespace.
 	 */
 	scontext = sepgsql_get_client_label();
-	tcontext = sepgsql_get_label(NamespaceRelationId, namespaceId, 0);
+	tcontext = sepgsql_get_label(NamespaceRelationId,
+								 proForm->pronamespace, 0);
 	ncontext = sepgsql_compute_create(scontext, tcontext,
 									  SEPG_CLASS_DB_PROCEDURE);
 
 	/*
+	 * check db_procedure:{create} permission
+	 */
+	initStringInfo(&audit_name);
+	appendStringInfo(&audit_name, "function %s(", NameStr(proForm->proname));
+	for (i=0; i < proForm->pronargs; i++)
+	{
+		Oid		typeoid = proForm->proargtypes.values[i];
+		if (i > 0)
+			appendStringInfoChar(&audit_name, ',');
+		appendStringInfoString(&audit_name, format_type_be(typeoid));
+	}
+	appendStringInfoChar(&audit_name, ')');
+
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_PROCEDURE,
+								  SEPG_DB_PROCEDURE__CREATE,
+								  audit_name.data,
+								  true);
+	/*
 	 * Assign the default security label on a new procedure
 	 */
 	object.classId = ProcedureRelationId;
@@ -83,6 +119,13 @@ sepgsql_proc_post_create(Oid functionId)
 	object.objectSubId = 0;
 	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
 
+	/*
+	 * Cleanup
+	 */
+	systable_endscan(sscan);
+	heap_close(rel, AccessShareLock);
+
+	pfree(audit_name.data);
 	pfree(tcontext);
 	pfree(ncontext);
 }
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index 9a1db91..b0695b4 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -31,6 +31,13 @@ CREATE SEQUENCE regtest_seq;
 
 CREATE TYPE regtest_comptype AS (a int, b text);
 
+CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql
+	   AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END';
+
+CREATE AGGREGATE regtest_agg (
+           sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0'
+);
+
 --
 -- clean-up
 --
pgsql-v9.2-sepgsql-create-permissions-part-2.v2.schema.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-create-permissions-part-2.v2.schema.patchDownload
 contrib/sepgsql/expected/create.out |    4 +++
 contrib/sepgsql/schema.c            |   50 ++++++++++++++++++++++++++++++++---
 contrib/sepgsql/sql/create.sql      |    6 ++++
 3 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index 8ff8c77..dbe0591 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -13,7 +13,11 @@ SET client_min_messages = LOG;
 CREATE DATABASE regtest_sepgsql_test_database;
 LOG:  SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template1"
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database regtest_sepgsql_test_database"
+CREATE SCHEMA regtest_schema;
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+SET search_path = regtest_schema, public;
 --
 -- clean-up
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
+DROP SCHEMA IF EXISTS regtest_schema CASCADE;
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index a167be1..c8bb8c9 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -10,12 +10,18 @@
  */
 #include "postgres.h"
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/sysattr.h"
 #include "catalog/dependency.h"
+#include "catalog/indexing.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "commands/seclabel.h"
 #include "miscadmin.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/tqual.h"
 
 #include "sepgsql.h"
 
@@ -28,19 +34,55 @@
 void
 sepgsql_schema_post_create(Oid namespaceId)
 {
-	char	   *scontext;
+	Relation	rel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
 	char	   *tcontext;
 	char	   *ncontext;
-	ObjectAddress object;
+	char		audit_name[NAMEDATALEN + 20];
+	ObjectAddress		object;
+	Form_pg_namespace	nspForm;
 
 	/*
 	 * Compute a default security label when we create a new schema object
 	 * under the working database.
+	 *
+	 * XXX - uncoming version of libselinux supports to take object
+	 * name to handle special treatment on default security label;
+	 * such as special label on "pg_temp" schema.
 	 */
-	scontext = sepgsql_get_client_label();
+	rel = heap_open(NamespaceRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(namespaceId));
+
+	sscan = systable_beginscan(rel, NamespaceOidIndexId, true,
+							   SnapshotSelf, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "catalog lookup failed for namespace %u", namespaceId);
+
+	nspForm = (Form_pg_namespace) GETSTRUCT(tuple);
+
 	tcontext = sepgsql_get_label(DatabaseRelationId, MyDatabaseId, 0);
-	ncontext = sepgsql_compute_create(scontext, tcontext,
+	ncontext = sepgsql_compute_create(sepgsql_get_client_label(),
+									  tcontext,
 									  SEPG_CLASS_DB_SCHEMA);
+	/*
+	 * check db_schema:{create}
+	 */
+	snprintf(audit_name, sizeof(audit_name),
+			 "schema %s", NameStr(nspForm->nspname));
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_SCHEMA,
+								  SEPG_DB_SCHEMA__CREATE,
+								  audit_name,
+								  true);
+	systable_endscan(sscan);
+	heap_close(rel, AccessShareLock);
 
 	/*
 	 * Assign the default security label on a new procedure
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index 6cd5656..a03c977 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -9,7 +9,13 @@ SET client_min_messages = LOG;
 
 CREATE DATABASE regtest_sepgsql_test_database;
 
+CREATE SCHEMA regtest_schema;
+
+SET search_path = regtest_schema, public;
+
 --
 -- clean-up
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
+
+DROP SCHEMA IF EXISTS regtest_schema CASCADE;
pgsql-v9.2-sepgsql-create-permissions-part-3.v2.relation.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-create-permissions-part-3.v2.relation.patchDownload
 contrib/sepgsql/expected/create.out |   46 +++++++++++
 contrib/sepgsql/hooks.c             |   70 +++++++++++++++++-
 contrib/sepgsql/relation.c          |  144 +++++++++++++++++++++++++++++------
 contrib/sepgsql/sql/create.sql      |   18 +++++
 4 files changed, 254 insertions(+), 24 deletions(-)

diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
index dbe0591..9b5c3d1 100644
--- a/contrib/sepgsql/expected/create.out
+++ b/contrib/sepgsql/expected/create.out
@@ -16,8 +16,54 @@ LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_
 CREATE SCHEMA regtest_schema;
 LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
 SET search_path = regtest_schema, public;
+CREATE TABLE regtest_table (x serial primary key, y text);
+NOTICE:  CREATE TABLE will create implicit sequence "regtest_table_x_seq" for serial column "regtest_table.x"
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_table_x_seq"
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="table regtest_table"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column tableoid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmax"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmax"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmin"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmin"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column ctid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column x"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column y"
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "regtest_table_pkey" for table "regtest_table"
+ALTER TABLE regtest_table ADD COLUMN z int;
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column z"
+CREATE TABLE regtest_table_2 (a int) WITH OIDS;
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="table regtest_table_2"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column tableoid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column cmax"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column xmax"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column cmin"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column xmin"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column oid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column ctid"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column a"
+-- corresponding toast table should not have label and permission checks
+ALTER TABLE regtest_table_2 ADD COLUMN b text;
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column b"
+-- VACUUM FULL internally create a new table and swap them later.
+VACUUM FULL regtest_table;
+CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100;
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="view regtest_view"
+CREATE SEQUENCE regtest_seq;
+LOG:  SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_seq"
+CREATE TYPE regtest_comptype AS (a int, b text);
 --
 -- clean-up
 --
 DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
 DROP SCHEMA IF EXISTS regtest_schema CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table regtest_table
+drop cascades to table regtest_table_2
+drop cascades to view regtest_view
+drop cascades to sequence regtest_seq
+drop cascades to type regtest_comptype
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index fe71953..8232971 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -41,6 +41,7 @@ static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
 static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
 static fmgr_hook_type next_fmgr_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
 
 /*
  * Contextual information on DDL commands
@@ -153,7 +154,30 @@ sepgsql_object_access(ObjectAccessType access,
 
 				case RelationRelationId:
 					if (subId == 0)
-						sepgsql_relation_post_create(objectId);
+					{
+						/*
+						 * All cases we want to apply permission checks on
+						 * creation of a new relation are invocation of the
+						 * heap_create_with_catalog via DefineRelation or
+						 * OpenIntoRel.
+						 * Elsewhere, we need neither assignment of security
+						 * label nor permission checks.
+						 */
+						switch (sepgsql_context_info.cmdtype)
+						{
+							case T_CreateStmt:
+							case T_ViewStmt:
+							case T_CreateSeqStmt:
+							case T_CompositeTypeStmt:
+							case T_CreateForeignTableStmt:
+							case T_SelectStmt:
+								sepgsql_relation_post_create(objectId);
+								break;
+							default:
+								/* via make_new_heap() */
+								break;
+						}
+					}
 					else
 						sepgsql_attribute_post_create(objectId, subId);
 					break;
@@ -312,6 +336,46 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 }
 
 /*
+ * sepgsql_executor_start
+ *
+ * It saves contextual information during ExecutorStart to distinguish 
+ * a case with/without permission checks later.
+ */
+static void
+sepgsql_executor_start(QueryDesc *queryDesc, int eflags)
+{
+	sepgsql_context_info_t	saved_context_info = sepgsql_context_info;
+
+	PG_TRY();
+	{
+		if (queryDesc->operation == CMD_SELECT)
+			sepgsql_context_info.cmdtype = T_SelectStmt;
+		else if (queryDesc->operation == CMD_INSERT)
+			sepgsql_context_info.cmdtype = T_InsertStmt;
+		else if (queryDesc->operation == CMD_DELETE)
+			sepgsql_context_info.cmdtype = T_DeleteStmt;
+		else if (queryDesc->operation == CMD_UPDATE)
+			sepgsql_context_info.cmdtype = T_UpdateStmt;
+
+		/*
+		 * XXX - If queryDesc->operation is not above four cases, an error
+		 * shall be raised on the following executor stage soon.
+		 */
+		if (next_ExecutorStart_hook)
+			(*next_ExecutorStart_hook) (queryDesc, eflags);
+		else
+			standard_ExecutorStart(queryDesc, eflags);
+	}
+	PG_CATCH();
+	{
+		sepgsql_context_info = saved_context_info;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+	sepgsql_context_info = saved_context_info;
+}
+
+/*
  * sepgsql_utility_command
  *
  * It tries to rough-grained control on utility commands; some of them can
@@ -504,6 +568,10 @@ _PG_init(void)
 	next_ProcessUtility_hook = ProcessUtility_hook;
 	ProcessUtility_hook = sepgsql_utility_command;
 
+	/* ExecutorStart hook */
+	next_ExecutorStart_hook = ExecutorStart_hook;
+	ExecutorStart_hook = sepgsql_executor_start;
+
 	/* init contextual info */
 	memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
 }
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 0767382..b4abc8e 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -36,10 +36,16 @@
 void
 sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 {
-	char	   *scontext = sepgsql_get_client_label();
+	Relation	rel;
+	ScanKeyData skey[2];
+	SysScanDesc sscan;
+	HeapTuple	tuple;
+	char	   *scontext;
 	char	   *tcontext;
 	char	   *ncontext;
+	char		audit_name[2*NAMEDATALEN + 20];
 	ObjectAddress object;
+	Form_pg_attribute	attForm;
 
 	/*
 	 * Only attributes within regular relation have individual security
@@ -49,13 +55,44 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 		return;
 
 	/*
-	 * Compute a default security label when we create a new procedure object
-	 * under the specified namespace.
+	 * Compute a default security label of the new column underlying the
+	 * specified relation, and check permission to create it.
 	 */
+	rel = heap_open(AttributeRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey[0],
+				Anum_pg_attribute_attrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relOid));
+	ScanKeyInit(&skey[1],
+				Anum_pg_attribute_attnum,
+				BTEqualStrategyNumber, F_INT2EQ,
+				Int16GetDatum(attnum));
+
+	sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true,
+							   SnapshotSelf, 2, &skey[0]);
+
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "catalog lookup failed for column %d of relation %u",
+			 attnum, relOid);
+
+	attForm = (Form_pg_attribute) GETSTRUCT(tuple);
+
 	scontext = sepgsql_get_client_label();
 	tcontext = sepgsql_get_label(RelationRelationId, relOid, 0);
 	ncontext = sepgsql_compute_create(scontext, tcontext,
 									  SEPG_CLASS_DB_COLUMN);
+	/*
+	 * check db_column:{create} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name), "table %s column %s",
+			 get_rel_name(relOid), NameStr(attForm->attname));
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_COLUMN,
+								  SEPG_DB_COLUMN__CREATE,
+								  audit_name,
+								  true);
 
 	/*
 	 * Assign the default security label on a new procedure
@@ -65,6 +102,9 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 	object.objectSubId = attnum;
 	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
 
+	systable_endscan(sscan);
+	heap_close(rel, AccessShareLock);
+
 	pfree(tcontext);
 	pfree(ncontext);
 }
@@ -127,10 +167,12 @@ sepgsql_relation_post_create(Oid relOid)
 	Form_pg_class classForm;
 	ObjectAddress object;
 	uint16		tclass;
+	const char *tclass_text;
 	char	   *scontext;		/* subject */
 	char	   *tcontext;		/* schema */
 	char	   *rcontext;		/* relation */
 	char	   *ccontext;		/* column */
+	char		audit_name[2*NAMEDATALEN + 20];
 
 	/*
 	 * Fetch catalog record of the new relation. Because pg_class entry is not
@@ -152,16 +194,36 @@ sepgsql_relation_post_create(Oid relOid)
 
 	classForm = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classForm->relkind == RELKIND_RELATION)
-		tclass = SEPG_CLASS_DB_TABLE;
-	else if (classForm->relkind == RELKIND_SEQUENCE)
-		tclass = SEPG_CLASS_DB_SEQUENCE;
-	else if (classForm->relkind == RELKIND_VIEW)
-		tclass = SEPG_CLASS_DB_VIEW;
-	else
-		goto out;				/* No need to assign individual labels */
+	switch (classForm->relkind)
+	{
+		case RELKIND_RELATION:
+			tclass = SEPG_CLASS_DB_TABLE;
+			tclass_text = "table";
+			break;
+		case RELKIND_SEQUENCE:
+			tclass = SEPG_CLASS_DB_SEQUENCE;
+			tclass_text = "sequence";
+			break;
+		case RELKIND_VIEW:
+			tclass = SEPG_CLASS_DB_VIEW;
+			tclass_text = "view";
+			break;
+		default:
+			goto out;
+	}
 
 	/*
+	 * check db_schema:{add_name} permission of the namespace
+	 */
+	object.classId = NamespaceRelationId;
+	object.objectId = classForm->relnamespace;
+	object.objectSubId = 0;
+	sepgsql_avc_check_perms(&object,
+							SEPG_CLASS_DB_SCHEMA,
+							SEPG_DB_SCHEMA__ADD_NAME,
+							getObjectDescription(&object),
+							true);
+	/*
 	 * Compute a default security label when we create a new relation object
 	 * under the specified namespace.
 	 */
@@ -171,6 +233,16 @@ sepgsql_relation_post_create(Oid relOid)
 	rcontext = sepgsql_compute_create(scontext, tcontext, tclass);
 
 	/*
+	 * check db_xxx:{create} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name), "%s %s",
+			 tclass_text, NameStr(classForm->relname));
+	sepgsql_avc_check_perms_label(rcontext,
+								  tclass,
+								  SEPG_DB_DATABASE__CREATE,
+								  audit_name,
+								  true);
+	/*
 	 * Assign the default security label on the new relation
 	 */
 	object.classId = RelationRelationId;
@@ -184,26 +256,52 @@ sepgsql_relation_post_create(Oid relOid)
 	 */
 	if (classForm->relkind == RELKIND_RELATION)
 	{
-		AttrNumber	index;
+		Relation	arel;
+		ScanKeyData	akey;
+		SysScanDesc	ascan;
+		HeapTuple	atup;
+		Form_pg_attribute	attForm;
 
-		ccontext = sepgsql_compute_create(scontext, rcontext,
-										  SEPG_CLASS_DB_COLUMN);
-		for (index = FirstLowInvalidHeapAttributeNumber + 1;
-			 index <= classForm->relnatts;
-			 index++)
+		arel = heap_open(AttributeRelationId, AccessShareLock);
+
+		ScanKeyInit(&akey,
+					Anum_pg_attribute_attrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relOid));
+
+		ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true,
+								   SnapshotSelf, 1, &akey);
+
+		while (HeapTupleIsValid(atup = systable_getnext(ascan)))
 		{
-			if (index == InvalidAttrNumber)
-				continue;
+			attForm = (Form_pg_attribute) GETSTRUCT(atup);
 
-			if (index == ObjectIdAttributeNumber && !classForm->relhasoids)
-				continue;
+			snprintf(audit_name, sizeof(audit_name), "%s %s column %s",
+					 tclass_text,
+					 NameStr(classForm->relname),
+					 NameStr(attForm->attname));
+
+			ccontext = sepgsql_compute_create(scontext,
+											  rcontext,
+											  SEPG_CLASS_DB_COLUMN);
+			/*
+			 * check db_column:{create} permission
+			 */
+			sepgsql_avc_check_perms_label(ccontext,
+										  SEPG_CLASS_DB_COLUMN,
+										  SEPG_DB_COLUMN__CREATE,
+										  audit_name,
+										  true);
 
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
-			object.objectSubId = index;
+			object.objectSubId = attForm->attnum;
 			SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext);
+
+			pfree(ccontext);
 		}
-		pfree(ccontext);
+		systable_endscan(ascan);
+		heap_close(arel, AccessShareLock);
 	}
 	pfree(rcontext);
 out:
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
index a03c977..9a1db91 100644
--- a/contrib/sepgsql/sql/create.sql
+++ b/contrib/sepgsql/sql/create.sql
@@ -13,6 +13,24 @@ CREATE SCHEMA regtest_schema;
 
 SET search_path = regtest_schema, public;
 
+CREATE TABLE regtest_table (x serial primary key, y text);
+
+ALTER TABLE regtest_table ADD COLUMN z int;
+
+CREATE TABLE regtest_table_2 (a int) WITH OIDS;
+
+-- corresponding toast table should not have label and permission checks
+ALTER TABLE regtest_table_2 ADD COLUMN b text;
+
+-- VACUUM FULL internally create a new table and swap them later.
+VACUUM FULL regtest_table;
+
+CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100;
+
+CREATE SEQUENCE regtest_seq;
+
+CREATE TYPE regtest_comptype AS (a int, b text);
+
 --
 -- clean-up
 --
pgsql-v9.2-sepgsql-create-permissions-part-1.v2.database.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-create-permissions-part-1.v2.database.patchDownload
 contrib/sepgsql/database.c          |   91 ++++++++++++++++++++++----
 contrib/sepgsql/expected/create.out |   19 ++++++
 contrib/sepgsql/hooks.c             |  122 ++++++++++++++++++++++++----------
 contrib/sepgsql/sepgsql.h           |    3 +-
 contrib/sepgsql/sql/create.sql      |   15 ++++
 contrib/sepgsql/test_sepgsql        |    2 +-
 doc/src/sgml/sepgsql.sgml           |   30 ++++++++-
 7 files changed, 231 insertions(+), 51 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 7f15d9c..3faef63 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -10,33 +10,100 @@
  */
 #include "postgres.h"
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/pg_database.h"
+#include "catalog/indexing.h"
+#include "commands/dbcommands.h"
 #include "commands/seclabel.h"
+#include "utils/fmgroids.h"
+#include "utils/tqual.h"
 #include "sepgsql.h"
 
+/*
+ * sepgsql_database_post_create
+ *
+ * This routine assigns a default security label on a newly defined
+ * database, and check permission needed for its creation.
+ */
 void
-sepgsql_database_post_create(Oid databaseId)
+sepgsql_database_post_create(Oid databaseId, const char *dtemplate)
 {
-	char   *scontext = sepgsql_get_client_label();
-	char   *tcontext;
-	char   *ncontext;
-	ObjectAddress	object;
+	Relation	rel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+	char	   *tcontext;
+	char	   *ncontext;
+	char		audit_name[NAMEDATALEN + 20];
+	ObjectAddress		object;
+	Form_pg_database	datForm;
+
+	/*
+	 * Oid of the source database is not saved in pg_database catalog,
+	 * so we collect its identifier using contextual information.
+	 * If NULL, its default is "template1" according to createdb().
+	 */
+	if (!dtemplate)
+		dtemplate = "template1";
+
+	object.classId = DatabaseRelationId;
+	object.objectId = get_database_oid(dtemplate, false);
+	object.objectSubId = 0;
+
+	tcontext = sepgsql_get_label(object.classId,
+								 object.objectId,
+								 object.objectSubId);
+	/*
+	 * check db_database:{getattr} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name), "database %s", dtemplate);
+	sepgsql_avc_check_perms_label(tcontext,
+								  SEPG_CLASS_DB_DATABASE,
+								  SEPG_DB_DATABASE__GETATTR,
+								  audit_name,
+								  true);
 
 	/*
 	 * Compute a default security label of the newly created database
 	 * based on a pair of security label of client and source database.
 	 *
-	 * XXX - Right now, this logic uses "template1" as its source, because
-	 * here is no way to know the Oid of source database.
+	 * XXX - uncoming version of libselinux supports to take object
+	 * name to handle special treatment on default security label.
 	 */
-	object.classId = DatabaseRelationId;
-	object.objectId = TemplateDbOid;
-	object.objectSubId = 0;
-	tcontext = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG);
+	rel = heap_open(DatabaseRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(databaseId));
+
+	sscan = systable_beginscan(rel, DatabaseOidIndexId, true,
+							   SnapshotSelf, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "catalog lookup failed for database %u", databaseId);
+
+	datForm = (Form_pg_database) GETSTRUCT(tuple);
 
-	ncontext = sepgsql_compute_create(scontext, tcontext,
+	ncontext = sepgsql_compute_create(sepgsql_get_client_label(),
+									  tcontext,
 									  SEPG_CLASS_DB_DATABASE);
+	/*
+	 * check db_database:{create} permission
+	 */
+	snprintf(audit_name, sizeof(audit_name),
+			 "database %s", NameStr(datForm->datname));
+	sepgsql_avc_check_perms_label(ncontext,
+								  SEPG_CLASS_DB_DATABASE,
+								  SEPG_DB_DATABASE__CREATE,
+								  audit_name,
+								  true);
+
+	systable_endscan(sscan);
+	heap_close(rel, AccessShareLock);
 
 	/*
 	 * Assign the default security label on the new database
diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out
new file mode 100644
index 0000000..8ff8c77
--- /dev/null
+++ b/contrib/sepgsql/expected/create.out
@@ -0,0 +1,19 @@
+--
+-- Regression Test for Creation of Object Permission Checks
+--
+-- confirm required permissions using audit messages
+SELECT sepgsql_getcon();	-- confirm client privilege
+              sepgsql_getcon               
+-------------------------------------------
+ unconfined_u:unconfined_r:unconfined_t:s0
+(1 row)
+
+SET sepgsql.debug_audit = true;
+SET client_min_messages = LOG;
+CREATE DATABASE regtest_sepgsql_test_database;
+LOG:  SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template1"
+LOG:  SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database regtest_sepgsql_test_database"
+--
+-- clean-up
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 331bbd7..fe71953 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -43,6 +43,22 @@ static fmgr_hook_type next_fmgr_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
+ * Contextual information on DDL commands
+ */
+typedef struct
+{
+	NodeTag		cmdtype;
+
+	/*
+	 * Name of the template database given by users on CREATE DATABASE
+	 * command. Elsewhere (including the case of default) NULL.
+	 */
+	const char *createdb_dtemplate;
+} sepgsql_context_info_t;
+
+static sepgsql_context_info_t	sepgsql_context_info;
+
+/*
  * GUC: sepgsql.permissive = (on|off)
  */
 static bool sepgsql_permissive;
@@ -127,7 +143,8 @@ sepgsql_object_access(ObjectAccessType access,
 			switch (classId)
 			{
 				case DatabaseRelationId:
-					sepgsql_database_post_create(objectId);
+					sepgsql_database_post_create(objectId,
+								sepgsql_context_info.createdb_dtemplate);
 					break;
 
 				case NamespaceRelationId:
@@ -308,44 +325,74 @@ sepgsql_utility_command(Node *parsetree,
 						DestReceiver *dest,
 						char *completionTag)
 {
-	if (next_ProcessUtility_hook)
-		(*next_ProcessUtility_hook) (parsetree, queryString, params,
-									 isTopLevel, dest, completionTag);
+	sepgsql_context_info_t	saved_context_info = sepgsql_context_info;
+	ListCell	   *cell;
 
-	/*
-	 * Check command tag to avoid nefarious operations
-	 */
-	switch (nodeTag(parsetree))
+	PG_TRY();
 	{
-		case T_LoadStmt:
-
-			/*
-			 * We reject LOAD command across the board on enforcing mode,
-			 * because a binary module can arbitrarily override hooks.
-			 */
-			if (sepgsql_getenforce())
-			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-						 errmsg("SELinux: LOAD is not permitted")));
-			}
-			break;
-		default:
-
-			/*
-			 * Right now we don't check any other utility commands, because it
-			 * needs more detailed information to make access control decision
-			 * here, but we don't want to have two parse and analyze routines
-			 * individually.
-			 */
-			break;
+		/*
+		 * Check command tag to avoid nefarious operations, and save the
+		 * current contextual information to determine whether we should
+		 * apply permission checks here, or not.
+		 */
+		sepgsql_context_info.cmdtype = nodeTag(parsetree);
+
+		switch (nodeTag(parsetree))
+		{
+			case T_CreatedbStmt:
+				/*
+				 * We hope to reference name of the source database, but it
+				 * does not appear in system catalog. So, we save it here.
+				 */
+				foreach (cell, ((CreatedbStmt *) parsetree)->options)
+				{
+					DefElem	   *defel = (DefElem *) lfirst(cell);
+
+					if (strcmp(defel->defname, "template") == 0)
+					{
+						sepgsql_context_info.createdb_dtemplate
+							= strVal(defel->arg);
+						break;
+					}
+				}
+				break;
+
+			case T_LoadStmt:
+				/*
+				 * We reject LOAD command across the board on enforcing mode,
+				 * because a binary module can arbitrarily override hooks.
+				 */
+				if (sepgsql_getenforce())
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("SELinux: LOAD is not permitted")));
+				}
+				break;
+			default:
+				/*
+				 * Right now we don't check any other utility commands,
+				 * because it needs more detailed information to make access
+				 * control decision here, but we don't want to have two parse
+				 * and analyze routines individually.
+				 */
+				break;
+		}
+
+		if (next_ProcessUtility_hook)
+			(*next_ProcessUtility_hook) (parsetree, queryString, params,
+										 isTopLevel, dest, completionTag);
+		else
+			standard_ProcessUtility(parsetree, queryString, params,
+									isTopLevel, dest, completionTag);
 	}
-
-	/*
-	 * Original implementation
-	 */
-	standard_ProcessUtility(parsetree, queryString, params,
-							isTopLevel, dest, completionTag);
+	PG_CATCH();
+	{
+		sepgsql_context_info = saved_context_info;
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+	sepgsql_context_info = saved_context_info;
 }
 
 /*
@@ -456,4 +503,7 @@ _PG_init(void)
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
 	ProcessUtility_hook = sepgsql_utility_command;
+
+	/* init contextual info */
+	memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
 }
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index b4c1dfd..33b219f 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -286,7 +286,8 @@ extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort);
 /*
  * database.c
  */
-extern void sepgsql_database_post_create(Oid databaseId);
+extern void sepgsql_database_post_create(Oid databaseId,
+										 const char *dtemplate);
 extern void sepgsql_database_relabel(Oid databaseId, const char *seclabel);
 
 /*
diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql
new file mode 100644
index 0000000..6cd5656
--- /dev/null
+++ b/contrib/sepgsql/sql/create.sql
@@ -0,0 +1,15 @@
+--
+-- Regression Test for Creation of Object Permission Checks
+--
+
+-- confirm required permissions using audit messages
+-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0
+SET sepgsql.debug_audit = true;
+SET client_min_messages = LOG;
+
+CREATE DATABASE regtest_sepgsql_test_database;
+
+--
+-- clean-up
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql
index 9b7262a..52237e6 100755
--- a/contrib/sepgsql/test_sepgsql
+++ b/contrib/sepgsql/test_sepgsql
@@ -259,6 +259,6 @@ echo "found ${NUM}"
 echo
 echo "============== running sepgsql regression tests       =============="
 
-make REGRESS="label dml misc" REGRESS_OPTS="--launcher ./launcher" installcheck
+make REGRESS="label dml create misc" REGRESS_OPTS="--launcher ./launcher" installcheck
 
 # exit with the exit code provided by "make"
diff --git a/doc/src/sgml/sepgsql.sgml b/doc/src/sgml/sepgsql.sgml
index f2c9266..e45c258 100644
--- a/doc/src/sgml/sepgsql.sgml
+++ b/doc/src/sgml/sepgsql.sgml
@@ -421,6 +421,33 @@ UPDATE t1 SET x = 2, y = md5sum(y) WHERE z = 100;
   <sect3>
    <title>DDL Permissions</title>
    <para>
+    <productname>SELinux</> defines several permissions to control common
+    operations for each object types; such as creation, alter, drop and
+    relabel of security label. In addition, several object types has its
+    special permissions to control its characteristic operations; such as
+    addition or deletion of name entries underlying a particular schema.
+   </para>
+   <para>
+    When <literal>CREATE</> command is executed, <literal>create</> will
+    be checked on the object being constructed for each object types.
+    A default security label shall be assigned on the new database object,
+    and the <literal>create</> permission needs to be allowed on the pair
+    of security label of the client and the new object itself.
+    We consider <xref linkend="sql-createtable"> construct a table and
+    underlying columns at the same time, so it requires users permission
+    to create both of table and columns.
+   </para>
+   <para>
+    A few additional checks are applied depending on object types.
+    On <xref linkend="sql-createdatabase">, <literal>getattr</> permission
+    shall be checked on the source or template database of the new database,
+    not only <literal>create</> on the new database.
+    On creation of objects underlying a particula schema (tables, views,
+    sequences and procedures), <literal>add_name</> shall be also chechked
+    on the schema, not only <literal>create</> on the new object itself.
+   </para>
+
+   <para>
     When <xref linkend="sql-security-label"> is executed, <literal>setattr</>
     and <literal>relabelfrom</> will be checked on the object being relabeled
     with its old security label, then <literal>relabelto</> with the supplied
@@ -509,7 +536,8 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer;
     <term>Data Definition Language (DDL) Permissions</term>
     <listitem>
      <para>
-      Due to implementation restrictions, DDL permissions are not checked.
+      Due to implementation restrictions, some of DDL permissions are not
+      checked.
      </para>
     </listitem>
    </varlistentry>
#16Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Kohei KaiGai (#15)
Re: Prep object creation hooks, and related sepgsql updates

Hi,

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

The attached patches are revised ones.
I added explanations of DDL permissions on creation time added by these patches,
and added a few regression test cases.

The whole patches are now against contrib/sepgsql, which seems to me to
be a good news, but means I'm not skilled to help review further. I'm
unsure about marking that as “ready for commiter” but I'm definitely
done myself.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

As seen here, contrib only patch now:

[2. application/octet-stream; pgsql-v9.2-sepgsql-create-permissions-part-4.v2.proc.patch]...

contrib/sepgsql/expected/create.out | 13 +++++++-
contrib/sepgsql/proc.c | 55 +++++++++++++++++++++++++++++++----
contrib/sepgsql/sql/create.sql | 7 ++++
3 files changed, 68 insertions(+), 7 deletions(-)

[3. application/octet-stream; pgsql-v9.2-sepgsql-create-permissions-part-2.v2.schema.patch]...

contrib/sepgsql/expected/create.out | 4 +++
contrib/sepgsql/schema.c | 50 ++++++++++++++++++++++++++++++++---
contrib/sepgsql/sql/create.sql | 6 ++++
3 files changed, 56 insertions(+), 4 deletions(-)

[4. application/octet-stream; pgsql-v9.2-sepgsql-create-permissions-part-3.v2.relation.patch]...

contrib/sepgsql/expected/create.out | 46 +++++++++++
contrib/sepgsql/hooks.c | 70 +++++++++++++++++-
contrib/sepgsql/relation.c | 144 +++++++++++++++++++++++++++++------
contrib/sepgsql/sql/create.sql | 18 +++++
4 files changed, 254 insertions(+), 24 deletions(-)

[5. application/octet-stream; pgsql-v9.2-sepgsql-create-permissions-part-1.v2.database.patch]...

contrib/sepgsql/database.c | 91 ++++++++++++++++++++++----
contrib/sepgsql/expected/create.out | 19 ++++++
contrib/sepgsql/hooks.c | 122 ++++++++++++++++++++++++----------
contrib/sepgsql/sepgsql.h | 3 +-
contrib/sepgsql/sql/create.sql | 15 ++++
contrib/sepgsql/test_sepgsql | 2 +-
doc/src/sgml/sepgsql.sgml | 30 ++++++++-
7 files changed, 231 insertions(+), 51 deletions(-)

#17Greg Smith
greg@2ndQuadrant.com
In reply to: Dimitri Fontaine (#16)
Re: Prep object creation hooks, and related sepgsql updates

On 12/16/2011 11:58 AM, Dimitri Fontaine wrote:

The whole patches are now against contrib/sepgsql, which seems to me to
be a good news, but means I'm not skilled to help review further. I'm
unsure about marking that as “ready for commiter” but I'm definitely
done myself.

Robert already took a brief look at this upthread and suggested it
seemed in the right direction if issues like the docs were sorted out.
That sounds like "ready for committer" going in his direction to me,
particularly since there's not a lot of other people who are familiar
with the sepgsql side; relabeling it in the CF app accordingly. Note
that these were labeled as "proof-of-concept" in the original
submission, so a commit might not be the right next step even if they
look good so far.

--
Greg Smith 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.us