pgsql-v9.2-prep-creation-hook-part-3.v1.patch

application/octet-stream

Filename: pgsql-v9.2-prep-creation-hook-part-3.v1.patch
Type: application/octet-stream
Part: 2
Message: Re: [v9.2] Object access hooks with arguments support (v1)

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v9
File+
contrib/sepgsql/expected/create.out 28 0
contrib/sepgsql/hooks.c 10 1
contrib/sepgsql/relation.c 143 70
contrib/sepgsql/sepgsql.h 10 1
contrib/sepgsql/sql/create.sql 8 0
contrib/sepgsql/test_sepgsql 28 0
src/backend/bootstrap/bootparse.y 2 1
src/backend/catalog/heap.c 5 2
src/backend/catalog/toasting.c 2 1
src/backend/commands/cluster.c 2 1
src/backend/commands/tablecmds.c 7 1
src/backend/executor/execMain.c 10 1
src/include/catalog/heap.h 2 1
src/include/catalog/objectaccess.h 30 0
 contrib/sepgsql/expected/create.out |   28 +++++
 contrib/sepgsql/hooks.c             |   11 ++-
 contrib/sepgsql/relation.c          |  213 +++++++++++++++++++++++------------
 contrib/sepgsql/sepgsql.h           |   11 ++-
 contrib/sepgsql/sql/create.sql      |    8 ++
 contrib/sepgsql/test_sepgsql        |   28 +++++
 src/backend/bootstrap/bootparse.y   |    3 +-
 src/backend/catalog/heap.c          |    7 +-
 src/backend/catalog/toasting.c      |    3 +-
 src/backend/commands/cluster.c      |    3 +-
 src/backend/commands/tablecmds.c    |    8 +-
 src/backend/executor/execMain.c     |   11 ++-
 src/include/catalog/heap.h          |    3 +-
 src/include/catalog/objectaccess.h  |   30 +++++
 14 files changed, 287 insertions(+), 80 deletions(-)

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